Skip to content

Commit f15ce99

Browse files
committed
fixes for alert api & forms
1 parent 1b7201e commit f15ce99

File tree

4 files changed

+100
-20
lines changed

4 files changed

+100
-20
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* Plugin System
1111
* Credential categories
1212
* Fix for SSH-RSA key usage (*error in libcrypto*)
13+
* Fix for [Log-View API usage](https://github.com/ansibleguy/webui/issues/36)
1314

1415
----
1516

CONTRIBUTE.md

+56
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,59 @@ python3 -m pip install -r ${REPO}/requirements_test.txt
114114
bash ${REPO}/scripts/lint.sh
115115
bash ${REPO}/scripts/test.sh
116116
```
117+
118+
----
119+
120+
## API
121+
122+
### Many-to-Many relations
123+
124+
DRF serializing is a little harder for many-to-many relations.
125+
126+
To make it work:
127+
128+
1. Initialize the choices for correct validation - example:
129+
130+
```python3
131+
class BaseAlertWriteRequest(serializers.ModelSerializer):
132+
def __init__(self, *args, **kwargs):
133+
super().__init__(*args, **kwargs)
134+
self.fields['jobs'] = serializers.MultipleChoiceField(choices=[job.id for job in Job.objects.all()])
135+
136+
jobs = serializers.MultipleChoiceField(allow_blank=True, choices=[])
137+
```
138+
139+
2. The update of the FK has to be done manually - example:
140+
141+
```python3
142+
def update_jobs(alert: BaseAlert, job_ids: list):
143+
jobs = []
144+
for job_id in job_ids:
145+
try:
146+
jobs.append(Job.objects.get(id=job_id))
147+
148+
except ObjectDoesNotExist:
149+
continue
150+
151+
alert.jobs.set(jobs)
152+
153+
update_jobs(alert=alert, job_ids=serializer.validated_data.pop('jobs'))
154+
AlertGlobal.objects.filter(id=alert.id).update(**serializer.validated_data)
155+
```
156+
157+
----
158+
159+
### Unique constraints
160+
161+
DRF has some issues with validating UC's set at model level.
162+
163+
To work around this - we can disable this validation:
164+
165+
```python3
166+
class RepositoryWriteRequest(serializers.ModelSerializer):
167+
class Meta:
168+
model = Repository
169+
fields = Repository.api_fields_write
170+
171+
name = serializers.CharField(validators=[]) # uc on update
172+
```

src/ansibleguy-webui/aw/api_endpoints/alert.py

+29-10
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,23 @@
55
from rest_framework.response import Response
66
from drf_spectacular.utils import extend_schema, OpenApiResponse
77

8+
from aw.model.job import Job
89
from aw.api_endpoints.base import API_PERMISSION, GenericResponse, get_api_user, api_docs_put, api_docs_delete, \
910
api_docs_post
1011
from aw.utils.permission import has_manager_privileges
11-
from aw.model.alert import AlertPlugin, AlertGlobal, AlertGroup, AlertUser
12+
from aw.model.alert import BaseAlert, AlertPlugin, AlertGlobal, AlertGroup, AlertUser
13+
14+
15+
def update_jobs(alert: BaseAlert, job_ids: list):
16+
jobs = []
17+
for job_id in job_ids:
18+
try:
19+
jobs.append(Job.objects.get(id=job_id))
20+
21+
except ObjectDoesNotExist:
22+
continue
23+
24+
alert.jobs.set(jobs)
1225

1326

1427
class AlertPluginReadWrite(serializers.ModelSerializer):
@@ -161,6 +174,15 @@ def delete(self, request, plugin_id: int):
161174
return Response(data={'msg': f"Alert-Plugin with ID {plugin_id} does not exist"}, status=404)
162175

163176

177+
class BaseAlertWriteRequest(serializers.ModelSerializer):
178+
def __init__(self, *args, **kwargs):
179+
super().__init__(*args, **kwargs)
180+
self.fields['jobs'] = serializers.MultipleChoiceField(choices=[job.id for job in Job.objects.all()])
181+
182+
name = serializers.CharField(validators=[]) # uc on update
183+
jobs = serializers.MultipleChoiceField(allow_blank=True, choices=[])
184+
185+
164186
class AlertUserReadResponse(serializers.ModelSerializer):
165187
class Meta:
166188
model = AlertUser
@@ -170,13 +192,11 @@ class Meta:
170192
condition_name = serializers.CharField()
171193

172194

173-
class AlertUserWriteRequest(serializers.ModelSerializer):
195+
class AlertUserWriteRequest(BaseAlertWriteRequest):
174196
class Meta:
175197
model = AlertUser
176198
fields = AlertUser.api_fields_write
177199

178-
name = serializers.CharField(validators=[]) # uc on update
179-
180200

181201
class APIAlertUser(GenericAPIView):
182202
http_method_names = ['get', 'post']
@@ -285,6 +305,7 @@ def put(self, request, alert_id: int):
285305
)
286306

287307
try:
308+
update_jobs(alert=alert, job_ids=serializer.validated_data.pop('jobs'))
288309
AlertUser.objects.filter(id=alert.id).update(
289310
**{**serializer.validated_data, 'user': user.id}
290311
)
@@ -326,13 +347,11 @@ class Meta:
326347
condition_name = serializers.CharField()
327348

328349

329-
class AlertGlobalWriteRequest(serializers.ModelSerializer):
350+
class AlertGlobalWriteRequest(BaseAlertWriteRequest):
330351
class Meta:
331352
model = AlertGlobal
332353
fields = AlertGlobal.api_fields_write
333354

334-
name = serializers.CharField(validators=[]) # uc on update
335-
336355

337356
class APIAlertGlobal(GenericAPIView):
338357
http_method_names = ['get', 'post']
@@ -446,6 +465,7 @@ def put(self, request, alert_id: int):
446465
)
447466

448467
try:
468+
update_jobs(alert=alert, job_ids=serializer.validated_data.pop('jobs'))
449469
AlertGlobal.objects.filter(id=alert.id).update(**serializer.validated_data)
450470
return Response(data={'msg': f"Alert '{alert.name}' updated"}, status=200)
451471

@@ -488,13 +508,11 @@ class Meta:
488508
group_name = serializers.CharField()
489509

490510

491-
class AlertGroupWriteRequest(serializers.ModelSerializer):
511+
class AlertGroupWriteRequest(BaseAlertWriteRequest):
492512
class Meta:
493513
model = AlertGroup
494514
fields = AlertGroup.api_fields_write
495515

496-
name = serializers.CharField(validators=[]) # uc on update
497-
498516

499517
class APIAlertGroup(GenericAPIView):
500518
http_method_names = ['get', 'post']
@@ -614,6 +632,7 @@ def put(self, request, alert_id: int):
614632
)
615633

616634
try:
635+
update_jobs(alert=alert, job_ids=serializer.validated_data.pop('jobs'))
617636
AlertGroup.objects.filter(id=alert.id).update(**serializer.validated_data)
618637
return Response(data={'msg': f"Alert '{alert.name}' updated"}, status=200)
619638

src/ansibleguy-webui/aw/views/forms/settings.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from aw.utils.http import ui_endpoint_wrapper_kwargs
77
from aw.model.permission import JobPermission, JobPermissionMapping, JobCredentialsPermissionMapping, \
88
JobRepositoryPermissionMapping, JobPermissionMemberUser, JobPermissionMemberGroup
9-
from aw.model.alert import AlertUser, AlertPlugin, AlertGroup, AlertGlobal
9+
from aw.model.alert import AlertUser, AlertPlugin, AlertGroup, AlertGlobal, \
10+
AlertUserJobMapping, AlertGroupJobMapping, AlertGlobalJobMapping
1011
from aw.config.form_metadata import FORM_LABEL, FORM_HELP
1112
from aw.views.base import choices_global_credentials, choices_job, choices_user, choices_group, choices_repositories
1213

@@ -147,20 +148,21 @@ def setting_alert_user_edit(request, alert_id: int = None) -> HttpResponse:
147148
perm_form = SettingAlertUserForm()
148149
form_method = 'post'
149150
form_api = 'alert/user'
150-
alert = {}
151+
data = {}
151152

152153
if alert_id is not None and alert_id != 0:
153154
alert = AlertUser.objects.filter(id=alert_id).first()
154155
if alert is None:
155156
return redirect(f"/ui/settings/alerts?error=Alert with ID {alert_id} does not exist")
156157

157-
alert = alert.__dict__
158+
data = alert.__dict__
159+
data['jobs'] = [link.job.id for link in AlertUserJobMapping.objects.filter(alert=alert)]
158160
form_method = 'put'
159161
form_api += f'/{alert_id}'
160162

161163
perm_form_html = perm_form.render(
162164
template_name='forms/snippet.html',
163-
context={'form': perm_form, 'existing': alert},
165+
context={'form': perm_form, 'existing': data},
164166
)
165167
return render(
166168
request, status=200, template_name='settings/alert_edit.html',
@@ -199,20 +201,21 @@ def setting_alert_group_edit(request, alert_id: int = None) -> HttpResponse:
199201
perm_form = SettingAlertGroupForm()
200202
form_method = 'post'
201203
form_api = 'alert/group'
202-
alert = {}
204+
data = {}
203205

204206
if alert_id is not None and alert_id != 0:
205207
alert = AlertGroup.objects.filter(id=alert_id).first()
206208
if alert is None:
207209
return redirect(f"/ui/settings/alerts?error=Alert with ID {alert_id} does not exist")
208210

209-
alert = alert.__dict__
211+
data = alert.__dict__
212+
data['jobs'] = [link.job.id for link in AlertGroupJobMapping.objects.filter(alert=alert)]
210213
form_method = 'put'
211214
form_api += f'/{alert_id}'
212215

213216
perm_form_html = perm_form.render(
214217
template_name='forms/snippet.html',
215-
context={'form': perm_form, 'existing': alert},
218+
context={'form': perm_form, 'existing': data},
216219
)
217220
return render(
218221
request, status=200, template_name='settings/alert_edit.html',
@@ -246,20 +249,21 @@ def setting_alert_global_edit(request, alert_id: int = None) -> HttpResponse:
246249
perm_form = SettingAlertUserForm()
247250
form_method = 'post'
248251
form_api = 'alert/global'
249-
alert = {}
252+
data = {}
250253

251254
if alert_id is not None and alert_id != 0:
252255
alert = AlertGlobal.objects.filter(id=alert_id).first()
253256
if alert is None:
254257
return redirect(f"/ui/settings/alerts?error=Alert with ID {alert_id} does not exist")
255258

256-
alert = alert.__dict__
259+
data = alert.__dict__
260+
data['jobs'] = [link.job.id for link in AlertGlobalJobMapping.objects.filter(alert=alert)]
257261
form_method = 'put'
258262
form_api += f'/{alert_id}'
259263

260264
perm_form_html = perm_form.render(
261265
template_name='forms/snippet.html',
262-
context={'form': perm_form, 'existing': alert},
266+
context={'form': perm_form, 'existing': data},
263267
)
264268
return render(
265269
request, status=200, template_name='settings/alert_edit.html',

0 commit comments

Comments
 (0)