Skip to content

Commit a8aa950

Browse files
committed
add project listing
1 parent 2f72919 commit a8aa950

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed

app/tests.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,61 @@ def testAccessFilesOnly(self):
842842
})
843843

844844

845+
class TestProjectsView(TestCase):
846+
def testListAllProjectsWithExpectedShape(self):
847+
from manifests.models import NodeData, NodeBuildProject
848+
849+
alpha = Project.objects.create(name="Alpha")
850+
beta = Project.objects.create(name="Beta")
851+
852+
alpha_user_1 = create_random_user()
853+
alpha_user_2 = create_random_user()
854+
beta_user = create_random_user()
855+
856+
UserMembership.objects.create(project=alpha, user=alpha_user_1)
857+
UserMembership.objects.create(project=alpha, user=alpha_user_2)
858+
UserMembership.objects.create(project=beta, user=beta_user)
859+
860+
alpha_node_1 = Node.objects.create(vsn="W001")
861+
alpha_node_2 = Node.objects.create(vsn="W002")
862+
beta_node = Node.objects.create(vsn="W010")
863+
864+
NodeMembership.objects.create(project=alpha, node=alpha_node_1)
865+
NodeMembership.objects.create(project=alpha, node=alpha_node_2)
866+
NodeMembership.objects.create(project=beta, node=beta_node)
867+
868+
# Create NodeData with projects from manifests
869+
sage_project = NodeBuildProject.objects.create(name="Sage")
870+
sgt_project = NodeBuildProject.objects.create(name="SGT")
871+
872+
NodeData.objects.create(vsn="W001", project=sage_project)
873+
NodeData.objects.create(vsn="W002", project=sgt_project)
874+
NodeData.objects.create(vsn="W010", project=sage_project)
875+
876+
# Login a user to access the authenticated endpoint
877+
self.client.force_login(alpha_user_1)
878+
879+
r = self.client.get("/projects/")
880+
self.assertEqual(r.status_code, status.HTTP_200_OK)
881+
882+
data = r.json()
883+
self.assertEqual([item["name"] for item in data], ["Alpha", "Beta"])
884+
885+
alpha_data = data[0]
886+
self.assertEqual(alpha_data["member_count"], 2)
887+
self.assertEqual(
888+
alpha_data["nodes"],
889+
[
890+
{"vsn": "W001", "project": "Sage"},
891+
{"vsn": "W002", "project": "SGT"},
892+
],
893+
)
894+
895+
beta_data = data[1]
896+
self.assertEqual(beta_data["member_count"], 1)
897+
self.assertEqual(beta_data["nodes"], [{"vsn": "W010", "project": "Sage"}])
898+
899+
845900
class TestAuth(TestCase):
846901
"""
847902
TestAuth tests that our post Globus login, create user and logout flows work as expected.

app/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@
5151
views.UserProjectsView.as_view(),
5252
name="user-projects",
5353
),
54+
path("projects/", views.ProjectsView.as_view(), name="projects"),
5455
# keeping old profiles/ path for now. replaced by users/.
5556
path(
5657
"profiles/<str:username>/access",
5758
views.UserAccessView.as_view(permission_classes=[AllowAny]),
5859
name="user-access-old",
5960
),
61+
6062
# keeping compatibility with existing portal user profiles
6163
path("user_profile/<str:username>", views.UserProfileView.as_view()),
6264
# feedback

app/views.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from rest_framework.permissions import IsAdminUser, IsAuthenticated, AllowAny
1919
from rest_framework.authtoken.models import Token
2020
from rest_framework import status
21+
from django.db.models import Count
2122
from django.contrib.auth import views as auth_views
2223
from django.contrib.auth.mixins import LoginRequiredMixin
2324
from django_slack import slack_message
@@ -192,6 +193,47 @@ def get(self, request: Request, username: str, format=None) -> Response:
192193
})
193194

194195

196+
class ProjectsView(APIView):
197+
permission_classes = [IsAuthenticated]
198+
199+
def get(self, request: Request, format=None) -> Response:
200+
from manifests.models import NodeData
201+
202+
projects = (
203+
Project.objects.annotate(member_count=Count("users", distinct=True))
204+
.prefetch_related("nodemembership_set__node")
205+
.order_by("name")
206+
)
207+
208+
data = []
209+
for project in projects:
210+
nodes = []
211+
for membership in sorted(
212+
project.nodemembership_set.all(),
213+
key=lambda m: (m.node.vsn or ""),
214+
):
215+
if membership.node.vsn:
216+
# Get the NodeData from manifests to access its project
217+
try:
218+
node_data = NodeData.objects.get(vsn=membership.node.vsn)
219+
node_project = node_data.project.name if node_data.project else None
220+
except NodeData.DoesNotExist:
221+
node_project = None
222+
223+
nodes.append({
224+
"vsn": membership.node.vsn,
225+
"project": node_project,
226+
})
227+
228+
data.append({
229+
"name": project.name,
230+
"nodes": nodes,
231+
"member_count": project.member_count,
232+
})
233+
234+
return Response(data)
235+
236+
195237
class NodeAuthorizedKeysView(APIView):
196238
permission_classes = [AllowAny]
197239

0 commit comments

Comments
 (0)