django-agent-studio 0.3.0__py3-none-any.whl → 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_agent_studio/agents/dynamic.py +2 -3
- django_agent_studio/api/serializers.py +139 -0
- django_agent_studio/api/urls.py +35 -0
- django_agent_studio/api/views.py +470 -8
- django_agent_studio/templates/django_agent_studio/agent_list.html +12 -5
- django_agent_studio/templates/django_agent_studio/builder.html +2 -2
- django_agent_studio/templates/django_agent_studio/collaborators.html +418 -0
- django_agent_studio/templates/django_agent_studio/home.html +21 -12
- django_agent_studio/templates/django_agent_studio/system_create.html +148 -0
- django_agent_studio/templates/django_agent_studio/system_list.html +15 -3
- django_agent_studio/templates/django_agent_studio/system_test.html +19 -17
- django_agent_studio/templates/django_agent_studio/test.html +9 -4
- django_agent_studio/urls.py +3 -0
- django_agent_studio/views.py +306 -15
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/METADATA +3 -1
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/RECORD +19 -17
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/WHEEL +0 -0
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/top_level.txt +0 -0
django_agent_studio/api/views.py
CHANGED
|
@@ -24,6 +24,10 @@ from django_agent_runtime.models import (
|
|
|
24
24
|
AgentSystemMember,
|
|
25
25
|
AgentSystemVersion,
|
|
26
26
|
AgentSystemSnapshot,
|
|
27
|
+
# Collaborator models
|
|
28
|
+
CollaboratorRole,
|
|
29
|
+
AgentCollaborator,
|
|
30
|
+
SystemCollaborator,
|
|
27
31
|
)
|
|
28
32
|
from django_agent_studio.api.serializers import (
|
|
29
33
|
AgentDefinitionListSerializer,
|
|
@@ -36,6 +40,11 @@ from django_agent_studio.api.serializers import (
|
|
|
36
40
|
DynamicToolExecutionSerializer,
|
|
37
41
|
ProjectScanRequestSerializer,
|
|
38
42
|
GenerateToolsRequestSerializer,
|
|
43
|
+
# Collaborator serializers
|
|
44
|
+
AgentCollaboratorSerializer,
|
|
45
|
+
SystemCollaboratorSerializer,
|
|
46
|
+
AddCollaboratorSerializer,
|
|
47
|
+
UpdateCollaboratorRoleSerializer,
|
|
39
48
|
)
|
|
40
49
|
|
|
41
50
|
|
|
@@ -43,16 +52,49 @@ from django_agent_studio.api.serializers import (
|
|
|
43
52
|
# Helper functions for consistent access control
|
|
44
53
|
# =============================================================================
|
|
45
54
|
|
|
55
|
+
from django.db.models import Q
|
|
56
|
+
|
|
57
|
+
|
|
46
58
|
def get_agent_for_user(user, agent_id):
|
|
47
59
|
"""
|
|
48
60
|
Get an agent if the user has access.
|
|
49
61
|
|
|
50
62
|
- Superusers can access any agent
|
|
51
|
-
-
|
|
63
|
+
- Owners can access their own agents
|
|
64
|
+
- Collaborators can access agents they've been granted access to
|
|
65
|
+
- Users with system access can access agents in that system
|
|
52
66
|
"""
|
|
53
67
|
if user.is_superuser:
|
|
54
68
|
return get_object_or_404(AgentDefinition, id=agent_id)
|
|
55
|
-
|
|
69
|
+
|
|
70
|
+
# Try to get the agent
|
|
71
|
+
agent = get_object_or_404(AgentDefinition, id=agent_id)
|
|
72
|
+
|
|
73
|
+
# Check if owner
|
|
74
|
+
if agent.owner == user:
|
|
75
|
+
return agent
|
|
76
|
+
|
|
77
|
+
# Check if direct collaborator on agent
|
|
78
|
+
if AgentCollaborator.objects.filter(agent=agent, user=user).exists():
|
|
79
|
+
return agent
|
|
80
|
+
|
|
81
|
+
# Check if user has access via a system that contains this agent
|
|
82
|
+
# Get all systems this agent belongs to
|
|
83
|
+
agent_system_ids = AgentSystemMember.objects.filter(
|
|
84
|
+
agent=agent
|
|
85
|
+
).values_list('system_id', flat=True)
|
|
86
|
+
|
|
87
|
+
if agent_system_ids:
|
|
88
|
+
# Check if user owns or is a collaborator on any of these systems
|
|
89
|
+
has_system_access = AgentSystem.objects.filter(
|
|
90
|
+
Q(id__in=agent_system_ids) &
|
|
91
|
+
(Q(owner=user) | Q(collaborators__user=user))
|
|
92
|
+
).exists()
|
|
93
|
+
if has_system_access:
|
|
94
|
+
return agent
|
|
95
|
+
|
|
96
|
+
# No access
|
|
97
|
+
raise Http404("Agent not found")
|
|
56
98
|
|
|
57
99
|
|
|
58
100
|
def get_agent_queryset_for_user(user):
|
|
@@ -60,11 +102,29 @@ def get_agent_queryset_for_user(user):
|
|
|
60
102
|
Get queryset of agents accessible to the user.
|
|
61
103
|
|
|
62
104
|
- Superusers can see all agents
|
|
63
|
-
- Regular users see
|
|
105
|
+
- Regular users see:
|
|
106
|
+
- Owned agents
|
|
107
|
+
- Agents where they are direct collaborators
|
|
108
|
+
- Agents in systems they own or are collaborators on
|
|
64
109
|
"""
|
|
65
110
|
if user.is_superuser:
|
|
66
111
|
return AgentDefinition.objects.all()
|
|
67
|
-
|
|
112
|
+
|
|
113
|
+
# Get system IDs the user has access to (owner or collaborator)
|
|
114
|
+
accessible_system_ids = AgentSystem.objects.filter(
|
|
115
|
+
Q(owner=user) | Q(collaborators__user=user)
|
|
116
|
+
).values_list('id', flat=True)
|
|
117
|
+
|
|
118
|
+
# Get agent IDs that are members of accessible systems
|
|
119
|
+
agents_via_systems = AgentSystemMember.objects.filter(
|
|
120
|
+
system_id__in=accessible_system_ids
|
|
121
|
+
).values_list('agent_id', flat=True)
|
|
122
|
+
|
|
123
|
+
return AgentDefinition.objects.filter(
|
|
124
|
+
Q(owner=user) |
|
|
125
|
+
Q(collaborators__user=user) |
|
|
126
|
+
Q(id__in=agents_via_systems)
|
|
127
|
+
).distinct()
|
|
68
128
|
|
|
69
129
|
|
|
70
130
|
def get_system_for_user(user, system_id):
|
|
@@ -72,11 +132,27 @@ def get_system_for_user(user, system_id):
|
|
|
72
132
|
Get a system if the user has access.
|
|
73
133
|
|
|
74
134
|
- Superusers can access any system
|
|
75
|
-
-
|
|
135
|
+
- Owners can access their own systems
|
|
136
|
+
- Collaborators can access systems they've been granted access to
|
|
76
137
|
"""
|
|
138
|
+
from django_agent_runtime.models import SystemCollaborator
|
|
139
|
+
|
|
77
140
|
if user.is_superuser:
|
|
78
141
|
return get_object_or_404(AgentSystem, id=system_id)
|
|
79
|
-
|
|
142
|
+
|
|
143
|
+
# Try to get the system
|
|
144
|
+
system = get_object_or_404(AgentSystem, id=system_id)
|
|
145
|
+
|
|
146
|
+
# Check if owner
|
|
147
|
+
if system.owner == user:
|
|
148
|
+
return system
|
|
149
|
+
|
|
150
|
+
# Check if collaborator
|
|
151
|
+
if SystemCollaborator.objects.filter(system=system, user=user).exists():
|
|
152
|
+
return system
|
|
153
|
+
|
|
154
|
+
# No access
|
|
155
|
+
raise Http404("System not found")
|
|
80
156
|
|
|
81
157
|
|
|
82
158
|
def get_system_queryset_for_user(user):
|
|
@@ -84,11 +160,13 @@ def get_system_queryset_for_user(user):
|
|
|
84
160
|
Get queryset of systems accessible to the user.
|
|
85
161
|
|
|
86
162
|
- Superusers can see all systems
|
|
87
|
-
- Regular users see
|
|
163
|
+
- Regular users see owned systems and systems where they are collaborators
|
|
88
164
|
"""
|
|
89
165
|
if user.is_superuser:
|
|
90
166
|
return AgentSystem.objects.all()
|
|
91
|
-
return AgentSystem.objects.filter(
|
|
167
|
+
return AgentSystem.objects.filter(
|
|
168
|
+
Q(owner=user) | Q(collaborators__user=user)
|
|
169
|
+
).distinct()
|
|
92
170
|
|
|
93
171
|
|
|
94
172
|
class AgentDefinitionListCreateView(generics.ListCreateAPIView):
|
|
@@ -2068,3 +2146,387 @@ class AgentSpecDocumentView(APIView):
|
|
|
2068
2146
|
'created': created,
|
|
2069
2147
|
'message': f"Spec {'created' if created else 'updated'} for {agent.name}",
|
|
2070
2148
|
})
|
|
2149
|
+
|
|
2150
|
+
|
|
2151
|
+
# =============================================================================
|
|
2152
|
+
# Collaborator Management Views
|
|
2153
|
+
# =============================================================================
|
|
2154
|
+
|
|
2155
|
+
from django_agent_runtime.models import (
|
|
2156
|
+
AgentCollaborator,
|
|
2157
|
+
SystemCollaborator,
|
|
2158
|
+
CollaboratorRole,
|
|
2159
|
+
)
|
|
2160
|
+
from django_agent_studio.api.serializers import (
|
|
2161
|
+
AgentCollaboratorSerializer,
|
|
2162
|
+
SystemCollaboratorSerializer,
|
|
2163
|
+
AddCollaboratorSerializer,
|
|
2164
|
+
UpdateCollaboratorRoleSerializer,
|
|
2165
|
+
)
|
|
2166
|
+
|
|
2167
|
+
|
|
2168
|
+
class AgentCollaboratorListCreateView(generics.ListCreateAPIView):
|
|
2169
|
+
"""
|
|
2170
|
+
List and add collaborators to an agent.
|
|
2171
|
+
|
|
2172
|
+
GET /api/agents/{id}/collaborators/
|
|
2173
|
+
POST /api/agents/{id}/collaborators/
|
|
2174
|
+
|
|
2175
|
+
Only owners and admins can manage collaborators.
|
|
2176
|
+
"""
|
|
2177
|
+
|
|
2178
|
+
permission_classes = [IsAuthenticated]
|
|
2179
|
+
|
|
2180
|
+
def get_serializer_class(self):
|
|
2181
|
+
if self.request.method == 'POST':
|
|
2182
|
+
return AddCollaboratorSerializer
|
|
2183
|
+
return AgentCollaboratorSerializer
|
|
2184
|
+
|
|
2185
|
+
def get_queryset(self):
|
|
2186
|
+
agent = get_agent_for_user(self.request.user, self.kwargs['agent_id'])
|
|
2187
|
+
return agent.collaborators.select_related('user', 'added_by').all()
|
|
2188
|
+
|
|
2189
|
+
def create(self, request, *args, **kwargs):
|
|
2190
|
+
agent = get_agent_for_user(request.user, self.kwargs['agent_id'])
|
|
2191
|
+
|
|
2192
|
+
# Check if user can manage collaborators (owner or admin)
|
|
2193
|
+
if not self._can_admin(request.user, agent):
|
|
2194
|
+
return Response(
|
|
2195
|
+
{'error': 'Only owners and admins can manage collaborators'},
|
|
2196
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
2197
|
+
)
|
|
2198
|
+
|
|
2199
|
+
serializer = self.get_serializer(data=request.data)
|
|
2200
|
+
serializer.is_valid(raise_exception=True)
|
|
2201
|
+
|
|
2202
|
+
# Find user by email or username
|
|
2203
|
+
from django.contrib.auth import get_user_model
|
|
2204
|
+
User = get_user_model()
|
|
2205
|
+
identifier = serializer.validated_data['email']
|
|
2206
|
+
|
|
2207
|
+
# Try to find user by email first, then username
|
|
2208
|
+
user = None
|
|
2209
|
+
user_fields = [f.name for f in User._meta.get_fields()]
|
|
2210
|
+
|
|
2211
|
+
if 'email' in user_fields:
|
|
2212
|
+
user = User.objects.filter(email=identifier).first()
|
|
2213
|
+
if not user and 'username' in user_fields:
|
|
2214
|
+
user = User.objects.filter(username=identifier).first()
|
|
2215
|
+
|
|
2216
|
+
if not user:
|
|
2217
|
+
return Response(
|
|
2218
|
+
{'error': f"User '{identifier}' not found"},
|
|
2219
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
2220
|
+
)
|
|
2221
|
+
|
|
2222
|
+
# Can't add owner as collaborator
|
|
2223
|
+
if user == agent.owner:
|
|
2224
|
+
return Response(
|
|
2225
|
+
{'error': 'Cannot add the owner as a collaborator'},
|
|
2226
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
2227
|
+
)
|
|
2228
|
+
|
|
2229
|
+
# Check if already a collaborator
|
|
2230
|
+
if AgentCollaborator.objects.filter(agent=agent, user=user).exists():
|
|
2231
|
+
return Response(
|
|
2232
|
+
{'error': 'User is already a collaborator on this agent'},
|
|
2233
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
2234
|
+
)
|
|
2235
|
+
|
|
2236
|
+
# Create collaborator
|
|
2237
|
+
collaborator = AgentCollaborator.objects.create(
|
|
2238
|
+
agent=agent,
|
|
2239
|
+
user=user,
|
|
2240
|
+
role=serializer.validated_data.get('role', CollaboratorRole.VIEWER),
|
|
2241
|
+
added_by=request.user,
|
|
2242
|
+
)
|
|
2243
|
+
|
|
2244
|
+
return Response(
|
|
2245
|
+
AgentCollaboratorSerializer(collaborator).data,
|
|
2246
|
+
status=status.HTTP_201_CREATED,
|
|
2247
|
+
)
|
|
2248
|
+
|
|
2249
|
+
def _can_admin(self, user, agent):
|
|
2250
|
+
"""Check if user can manage collaborators."""
|
|
2251
|
+
if user.is_superuser or agent.owner == user:
|
|
2252
|
+
return True
|
|
2253
|
+
try:
|
|
2254
|
+
collab = AgentCollaborator.objects.get(agent=agent, user=user)
|
|
2255
|
+
return collab.can_admin
|
|
2256
|
+
except AgentCollaborator.DoesNotExist:
|
|
2257
|
+
return False
|
|
2258
|
+
|
|
2259
|
+
|
|
2260
|
+
class AgentCollaboratorDetailView(generics.RetrieveUpdateDestroyAPIView):
|
|
2261
|
+
"""
|
|
2262
|
+
Retrieve, update, or delete an agent collaborator.
|
|
2263
|
+
|
|
2264
|
+
GET/PUT/PATCH/DELETE /api/agents/{id}/collaborators/{collaborator_id}/
|
|
2265
|
+
|
|
2266
|
+
Only owners and admins can manage collaborators.
|
|
2267
|
+
"""
|
|
2268
|
+
|
|
2269
|
+
permission_classes = [IsAuthenticated]
|
|
2270
|
+
serializer_class = AgentCollaboratorSerializer
|
|
2271
|
+
|
|
2272
|
+
def get_queryset(self):
|
|
2273
|
+
agent = get_agent_for_user(self.request.user, self.kwargs['agent_id'])
|
|
2274
|
+
return agent.collaborators.select_related('user', 'added_by').all()
|
|
2275
|
+
|
|
2276
|
+
def update(self, request, *args, **kwargs):
|
|
2277
|
+
agent = get_agent_for_user(request.user, self.kwargs['agent_id'])
|
|
2278
|
+
|
|
2279
|
+
if not self._can_admin(request.user, agent):
|
|
2280
|
+
return Response(
|
|
2281
|
+
{'error': 'Only owners and admins can manage collaborators'},
|
|
2282
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
2283
|
+
)
|
|
2284
|
+
|
|
2285
|
+
# Use the role update serializer for validation
|
|
2286
|
+
role_serializer = UpdateCollaboratorRoleSerializer(data=request.data)
|
|
2287
|
+
role_serializer.is_valid(raise_exception=True)
|
|
2288
|
+
|
|
2289
|
+
collaborator = self.get_object()
|
|
2290
|
+
collaborator.role = role_serializer.validated_data['role']
|
|
2291
|
+
collaborator.save()
|
|
2292
|
+
|
|
2293
|
+
return Response(AgentCollaboratorSerializer(collaborator).data)
|
|
2294
|
+
|
|
2295
|
+
def destroy(self, request, *args, **kwargs):
|
|
2296
|
+
agent = get_agent_for_user(request.user, self.kwargs['agent_id'])
|
|
2297
|
+
|
|
2298
|
+
if not self._can_admin(request.user, agent):
|
|
2299
|
+
return Response(
|
|
2300
|
+
{'error': 'Only owners and admins can manage collaborators'},
|
|
2301
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
2302
|
+
)
|
|
2303
|
+
|
|
2304
|
+
return super().destroy(request, *args, **kwargs)
|
|
2305
|
+
|
|
2306
|
+
def _can_admin(self, user, agent):
|
|
2307
|
+
"""Check if user can manage collaborators."""
|
|
2308
|
+
if user.is_superuser or agent.owner == user:
|
|
2309
|
+
return True
|
|
2310
|
+
try:
|
|
2311
|
+
collab = AgentCollaborator.objects.get(agent=agent, user=user)
|
|
2312
|
+
return collab.can_admin
|
|
2313
|
+
except AgentCollaborator.DoesNotExist:
|
|
2314
|
+
return False
|
|
2315
|
+
|
|
2316
|
+
|
|
2317
|
+
class SystemCollaboratorListCreateView(generics.ListCreateAPIView):
|
|
2318
|
+
"""
|
|
2319
|
+
List and add collaborators to a system.
|
|
2320
|
+
|
|
2321
|
+
GET /api/systems/{id}/collaborators/
|
|
2322
|
+
POST /api/systems/{id}/collaborators/
|
|
2323
|
+
|
|
2324
|
+
Only owners and admins can manage collaborators.
|
|
2325
|
+
"""
|
|
2326
|
+
|
|
2327
|
+
permission_classes = [IsAuthenticated]
|
|
2328
|
+
|
|
2329
|
+
def get_serializer_class(self):
|
|
2330
|
+
if self.request.method == 'POST':
|
|
2331
|
+
return AddCollaboratorSerializer
|
|
2332
|
+
return SystemCollaboratorSerializer
|
|
2333
|
+
|
|
2334
|
+
def get_queryset(self):
|
|
2335
|
+
system = get_system_for_user(self.request.user, self.kwargs['system_id'])
|
|
2336
|
+
return system.collaborators.select_related('user', 'added_by').all()
|
|
2337
|
+
|
|
2338
|
+
def create(self, request, *args, **kwargs):
|
|
2339
|
+
system = get_system_for_user(request.user, self.kwargs['system_id'])
|
|
2340
|
+
|
|
2341
|
+
# Check if user can manage collaborators (owner or admin)
|
|
2342
|
+
if not self._can_admin(request.user, system):
|
|
2343
|
+
return Response(
|
|
2344
|
+
{'error': 'Only owners and admins can manage collaborators'},
|
|
2345
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
2346
|
+
)
|
|
2347
|
+
|
|
2348
|
+
serializer = self.get_serializer(data=request.data)
|
|
2349
|
+
serializer.is_valid(raise_exception=True)
|
|
2350
|
+
|
|
2351
|
+
# Find user by email or username
|
|
2352
|
+
from django.contrib.auth import get_user_model
|
|
2353
|
+
User = get_user_model()
|
|
2354
|
+
identifier = serializer.validated_data['email']
|
|
2355
|
+
|
|
2356
|
+
# Try to find user by email first, then username
|
|
2357
|
+
user = None
|
|
2358
|
+
user_fields = [f.name for f in User._meta.get_fields()]
|
|
2359
|
+
|
|
2360
|
+
if 'email' in user_fields:
|
|
2361
|
+
user = User.objects.filter(email=identifier).first()
|
|
2362
|
+
if not user and 'username' in user_fields:
|
|
2363
|
+
user = User.objects.filter(username=identifier).first()
|
|
2364
|
+
|
|
2365
|
+
if not user:
|
|
2366
|
+
return Response(
|
|
2367
|
+
{'error': f"User '{identifier}' not found"},
|
|
2368
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
2369
|
+
)
|
|
2370
|
+
|
|
2371
|
+
# Can't add owner as collaborator
|
|
2372
|
+
if user == system.owner:
|
|
2373
|
+
return Response(
|
|
2374
|
+
{'error': 'Cannot add the owner as a collaborator'},
|
|
2375
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
2376
|
+
)
|
|
2377
|
+
|
|
2378
|
+
# Check if already a collaborator
|
|
2379
|
+
if SystemCollaborator.objects.filter(system=system, user=user).exists():
|
|
2380
|
+
return Response(
|
|
2381
|
+
{'error': 'User is already a collaborator on this system'},
|
|
2382
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
2383
|
+
)
|
|
2384
|
+
|
|
2385
|
+
# Create collaborator
|
|
2386
|
+
collaborator = SystemCollaborator.objects.create(
|
|
2387
|
+
system=system,
|
|
2388
|
+
user=user,
|
|
2389
|
+
role=serializer.validated_data.get('role', CollaboratorRole.VIEWER),
|
|
2390
|
+
added_by=request.user,
|
|
2391
|
+
)
|
|
2392
|
+
|
|
2393
|
+
return Response(
|
|
2394
|
+
SystemCollaboratorSerializer(collaborator).data,
|
|
2395
|
+
status=status.HTTP_201_CREATED,
|
|
2396
|
+
)
|
|
2397
|
+
|
|
2398
|
+
def _can_admin(self, user, system):
|
|
2399
|
+
"""Check if user can manage collaborators."""
|
|
2400
|
+
if user.is_superuser or system.owner == user:
|
|
2401
|
+
return True
|
|
2402
|
+
try:
|
|
2403
|
+
collab = SystemCollaborator.objects.get(system=system, user=user)
|
|
2404
|
+
return collab.can_admin
|
|
2405
|
+
except SystemCollaborator.DoesNotExist:
|
|
2406
|
+
return False
|
|
2407
|
+
|
|
2408
|
+
|
|
2409
|
+
class SystemCollaboratorDetailView(generics.RetrieveUpdateDestroyAPIView):
|
|
2410
|
+
"""
|
|
2411
|
+
Retrieve, update, or delete a system collaborator.
|
|
2412
|
+
|
|
2413
|
+
GET/PUT/PATCH/DELETE /api/systems/{id}/collaborators/{collaborator_id}/
|
|
2414
|
+
|
|
2415
|
+
Only owners and admins can manage collaborators.
|
|
2416
|
+
"""
|
|
2417
|
+
|
|
2418
|
+
permission_classes = [IsAuthenticated]
|
|
2419
|
+
serializer_class = SystemCollaboratorSerializer
|
|
2420
|
+
|
|
2421
|
+
def get_queryset(self):
|
|
2422
|
+
system = get_system_for_user(self.request.user, self.kwargs['system_id'])
|
|
2423
|
+
return system.collaborators.select_related('user', 'added_by').all()
|
|
2424
|
+
|
|
2425
|
+
def update(self, request, *args, **kwargs):
|
|
2426
|
+
system = get_system_for_user(request.user, self.kwargs['system_id'])
|
|
2427
|
+
|
|
2428
|
+
if not self._can_admin(request.user, system):
|
|
2429
|
+
return Response(
|
|
2430
|
+
{'error': 'Only owners and admins can manage collaborators'},
|
|
2431
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
2432
|
+
)
|
|
2433
|
+
|
|
2434
|
+
# Use the role update serializer for validation
|
|
2435
|
+
role_serializer = UpdateCollaboratorRoleSerializer(data=request.data)
|
|
2436
|
+
role_serializer.is_valid(raise_exception=True)
|
|
2437
|
+
|
|
2438
|
+
collaborator = self.get_object()
|
|
2439
|
+
collaborator.role = role_serializer.validated_data['role']
|
|
2440
|
+
collaborator.save()
|
|
2441
|
+
|
|
2442
|
+
return Response(SystemCollaboratorSerializer(collaborator).data)
|
|
2443
|
+
|
|
2444
|
+
def destroy(self, request, *args, **kwargs):
|
|
2445
|
+
system = get_system_for_user(request.user, self.kwargs['system_id'])
|
|
2446
|
+
|
|
2447
|
+
if not self._can_admin(request.user, system):
|
|
2448
|
+
return Response(
|
|
2449
|
+
{'error': 'Only owners and admins can manage collaborators'},
|
|
2450
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
2451
|
+
)
|
|
2452
|
+
|
|
2453
|
+
return super().destroy(request, *args, **kwargs)
|
|
2454
|
+
|
|
2455
|
+
def _can_admin(self, user, system):
|
|
2456
|
+
"""Check if user can manage collaborators."""
|
|
2457
|
+
if user.is_superuser or system.owner == user:
|
|
2458
|
+
return True
|
|
2459
|
+
try:
|
|
2460
|
+
collab = SystemCollaborator.objects.get(system=system, user=user)
|
|
2461
|
+
return collab.can_admin
|
|
2462
|
+
except SystemCollaborator.DoesNotExist:
|
|
2463
|
+
return False
|
|
2464
|
+
|
|
2465
|
+
|
|
2466
|
+
class UserSearchView(APIView):
|
|
2467
|
+
"""
|
|
2468
|
+
Search for users by email, username, or name for collaborator autocomplete.
|
|
2469
|
+
|
|
2470
|
+
GET /api/users/search/?q=<query>
|
|
2471
|
+
|
|
2472
|
+
Returns up to 10 matching users.
|
|
2473
|
+
Handles both email-based and username-based User models.
|
|
2474
|
+
"""
|
|
2475
|
+
|
|
2476
|
+
permission_classes = [IsAuthenticated]
|
|
2477
|
+
|
|
2478
|
+
def get(self, request):
|
|
2479
|
+
from django.contrib.auth import get_user_model
|
|
2480
|
+
from django.db.models import Q
|
|
2481
|
+
|
|
2482
|
+
User = get_user_model()
|
|
2483
|
+
query = request.query_params.get('q', '').strip()
|
|
2484
|
+
|
|
2485
|
+
if len(query) < 2:
|
|
2486
|
+
return Response([])
|
|
2487
|
+
|
|
2488
|
+
# Build query filters based on available fields
|
|
2489
|
+
# Check which fields exist on the User model
|
|
2490
|
+
user_fields = [f.name for f in User._meta.get_fields()]
|
|
2491
|
+
|
|
2492
|
+
filters = Q()
|
|
2493
|
+
if 'email' in user_fields:
|
|
2494
|
+
filters |= Q(email__icontains=query)
|
|
2495
|
+
if 'username' in user_fields:
|
|
2496
|
+
filters |= Q(username__icontains=query)
|
|
2497
|
+
if 'first_name' in user_fields:
|
|
2498
|
+
filters |= Q(first_name__icontains=query)
|
|
2499
|
+
if 'last_name' in user_fields:
|
|
2500
|
+
filters |= Q(last_name__icontains=query)
|
|
2501
|
+
if 'full_name' in user_fields:
|
|
2502
|
+
filters |= Q(full_name__icontains=query)
|
|
2503
|
+
|
|
2504
|
+
users = User.objects.filter(filters).exclude(
|
|
2505
|
+
id=request.user.id # Exclude current user
|
|
2506
|
+
)[:10]
|
|
2507
|
+
|
|
2508
|
+
results = []
|
|
2509
|
+
for user in users:
|
|
2510
|
+
# Get identifier (email or username)
|
|
2511
|
+
identifier = getattr(user, 'email', None) or getattr(user, 'username', None) or str(user)
|
|
2512
|
+
|
|
2513
|
+
# Get display name
|
|
2514
|
+
full_name = getattr(user, 'full_name', None) or ''
|
|
2515
|
+
first_name = getattr(user, 'first_name', None) or ''
|
|
2516
|
+
last_name = getattr(user, 'last_name', None) or ''
|
|
2517
|
+
name = full_name or f"{first_name} {last_name}".strip() or identifier
|
|
2518
|
+
|
|
2519
|
+
# Build display string
|
|
2520
|
+
if name != identifier:
|
|
2521
|
+
display = f"{name} ({identifier})"
|
|
2522
|
+
else:
|
|
2523
|
+
display = identifier
|
|
2524
|
+
|
|
2525
|
+
results.append({
|
|
2526
|
+
'id': str(user.id),
|
|
2527
|
+
'email': identifier, # Keep as 'email' for backward compatibility
|
|
2528
|
+
'name': name,
|
|
2529
|
+
'display': display,
|
|
2530
|
+
})
|
|
2531
|
+
|
|
2532
|
+
return Response(results)
|
|
@@ -58,11 +58,18 @@
|
|
|
58
58
|
</div>
|
|
59
59
|
</div>
|
|
60
60
|
<div class="border-t border-gray-100 px-4 py-3 bg-gray-50 flex items-center justify-between">
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
<div class="flex items-center space-x-3">
|
|
62
|
+
<a href="{% url 'agent_studio:agent_edit' agent.id %}"
|
|
63
|
+
class="text-primary-600 hover:text-primary-700 text-sm font-medium">
|
|
64
|
+
Edit
|
|
65
|
+
</a>
|
|
66
|
+
<a href="{% url 'agent_studio:agent_collaborators' agent.id %}"
|
|
67
|
+
class="text-gray-500 hover:text-gray-700 text-sm"
|
|
68
|
+
title="Manage collaborators">
|
|
69
|
+
<i class="pi pi-users"></i>
|
|
70
|
+
</a>
|
|
71
|
+
</div>
|
|
72
|
+
<a href="{% url 'agent_studio:agent_test' agent.id %}"
|
|
66
73
|
class="text-gray-600 hover:text-gray-700 text-sm">
|
|
67
74
|
Test →
|
|
68
75
|
</a>
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
<script>
|
|
26
26
|
// Configuration passed from Django
|
|
27
27
|
window.STUDIO_CONFIG = {
|
|
28
|
-
agentId: {% if agent %}"{{ agent.id }}"{% else %}null{% endif %},
|
|
28
|
+
agentId: {% if agent %}"{{ agent.id|escapejs }}"{% else %}null{% endif %},
|
|
29
29
|
csrfToken: "{{ csrf_token }}",
|
|
30
|
-
builderAgentKey: "{{ builder_agent_key }}",
|
|
30
|
+
builderAgentKey: "{{ builder_agent_key|escapejs }}",
|
|
31
31
|
};
|
|
32
32
|
</script>
|
|
33
33
|
|