Skip to content

Commit

Permalink
fixes for alert api & forms
Browse files Browse the repository at this point in the history
  • Loading branch information
ansibleguy committed May 12, 2024
1 parent 1b7201e commit f15ce99
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Plugin System
* Credential categories
* Fix for SSH-RSA key usage (*error in libcrypto*)
* Fix for [Log-View API usage](https://github.com/ansibleguy/webui/issues/36)

----

Expand Down
56 changes: 56 additions & 0 deletions CONTRIBUTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,59 @@ python3 -m pip install -r ${REPO}/requirements_test.txt
bash ${REPO}/scripts/lint.sh
bash ${REPO}/scripts/test.sh
```

----

## API

### Many-to-Many relations

DRF serializing is a little harder for many-to-many relations.

To make it work:

1. Initialize the choices for correct validation - example:

```python3
class BaseAlertWriteRequest(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['jobs'] = serializers.MultipleChoiceField(choices=[job.id for job in Job.objects.all()])

jobs = serializers.MultipleChoiceField(allow_blank=True, choices=[])
```

2. The update of the FK has to be done manually - example:

```python3
def update_jobs(alert: BaseAlert, job_ids: list):
jobs = []
for job_id in job_ids:
try:
jobs.append(Job.objects.get(id=job_id))

except ObjectDoesNotExist:
continue

alert.jobs.set(jobs)

update_jobs(alert=alert, job_ids=serializer.validated_data.pop('jobs'))
AlertGlobal.objects.filter(id=alert.id).update(**serializer.validated_data)
```

----

### Unique constraints

DRF has some issues with validating UC's set at model level.

To work around this - we can disable this validation:

```python3
class RepositoryWriteRequest(serializers.ModelSerializer):
class Meta:
model = Repository
fields = Repository.api_fields_write

name = serializers.CharField(validators=[]) # uc on update
```
39 changes: 29 additions & 10 deletions src/ansibleguy-webui/aw/api_endpoints/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema, OpenApiResponse

from aw.model.job import Job
from aw.api_endpoints.base import API_PERMISSION, GenericResponse, get_api_user, api_docs_put, api_docs_delete, \
api_docs_post
from aw.utils.permission import has_manager_privileges
from aw.model.alert import AlertPlugin, AlertGlobal, AlertGroup, AlertUser
from aw.model.alert import BaseAlert, AlertPlugin, AlertGlobal, AlertGroup, AlertUser


def update_jobs(alert: BaseAlert, job_ids: list):
jobs = []
for job_id in job_ids:
try:
jobs.append(Job.objects.get(id=job_id))

except ObjectDoesNotExist:
continue

alert.jobs.set(jobs)


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


class BaseAlertWriteRequest(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['jobs'] = serializers.MultipleChoiceField(choices=[job.id for job in Job.objects.all()])

name = serializers.CharField(validators=[]) # uc on update
jobs = serializers.MultipleChoiceField(allow_blank=True, choices=[])


class AlertUserReadResponse(serializers.ModelSerializer):
class Meta:
model = AlertUser
Expand All @@ -170,13 +192,11 @@ class Meta:
condition_name = serializers.CharField()


class AlertUserWriteRequest(serializers.ModelSerializer):
class AlertUserWriteRequest(BaseAlertWriteRequest):
class Meta:
model = AlertUser
fields = AlertUser.api_fields_write

name = serializers.CharField(validators=[]) # uc on update


class APIAlertUser(GenericAPIView):
http_method_names = ['get', 'post']
Expand Down Expand Up @@ -285,6 +305,7 @@ def put(self, request, alert_id: int):
)

try:
update_jobs(alert=alert, job_ids=serializer.validated_data.pop('jobs'))
AlertUser.objects.filter(id=alert.id).update(
**{**serializer.validated_data, 'user': user.id}
)
Expand Down Expand Up @@ -326,13 +347,11 @@ class Meta:
condition_name = serializers.CharField()


class AlertGlobalWriteRequest(serializers.ModelSerializer):
class AlertGlobalWriteRequest(BaseAlertWriteRequest):
class Meta:
model = AlertGlobal
fields = AlertGlobal.api_fields_write

name = serializers.CharField(validators=[]) # uc on update


class APIAlertGlobal(GenericAPIView):
http_method_names = ['get', 'post']
Expand Down Expand Up @@ -446,6 +465,7 @@ def put(self, request, alert_id: int):
)

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

Expand Down Expand Up @@ -488,13 +508,11 @@ class Meta:
group_name = serializers.CharField()


class AlertGroupWriteRequest(serializers.ModelSerializer):
class AlertGroupWriteRequest(BaseAlertWriteRequest):
class Meta:
model = AlertGroup
fields = AlertGroup.api_fields_write

name = serializers.CharField(validators=[]) # uc on update


class APIAlertGroup(GenericAPIView):
http_method_names = ['get', 'post']
Expand Down Expand Up @@ -614,6 +632,7 @@ def put(self, request, alert_id: int):
)

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

Expand Down
24 changes: 14 additions & 10 deletions src/ansibleguy-webui/aw/views/forms/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from aw.utils.http import ui_endpoint_wrapper_kwargs
from aw.model.permission import JobPermission, JobPermissionMapping, JobCredentialsPermissionMapping, \
JobRepositoryPermissionMapping, JobPermissionMemberUser, JobPermissionMemberGroup
from aw.model.alert import AlertUser, AlertPlugin, AlertGroup, AlertGlobal
from aw.model.alert import AlertUser, AlertPlugin, AlertGroup, AlertGlobal, \
AlertUserJobMapping, AlertGroupJobMapping, AlertGlobalJobMapping
from aw.config.form_metadata import FORM_LABEL, FORM_HELP
from aw.views.base import choices_global_credentials, choices_job, choices_user, choices_group, choices_repositories

Expand Down Expand Up @@ -147,20 +148,21 @@ def setting_alert_user_edit(request, alert_id: int = None) -> HttpResponse:
perm_form = SettingAlertUserForm()
form_method = 'post'
form_api = 'alert/user'
alert = {}
data = {}

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

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

perm_form_html = perm_form.render(
template_name='forms/snippet.html',
context={'form': perm_form, 'existing': alert},
context={'form': perm_form, 'existing': data},
)
return render(
request, status=200, template_name='settings/alert_edit.html',
Expand Down Expand Up @@ -199,20 +201,21 @@ def setting_alert_group_edit(request, alert_id: int = None) -> HttpResponse:
perm_form = SettingAlertGroupForm()
form_method = 'post'
form_api = 'alert/group'
alert = {}
data = {}

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

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

perm_form_html = perm_form.render(
template_name='forms/snippet.html',
context={'form': perm_form, 'existing': alert},
context={'form': perm_form, 'existing': data},
)
return render(
request, status=200, template_name='settings/alert_edit.html',
Expand Down Expand Up @@ -246,20 +249,21 @@ def setting_alert_global_edit(request, alert_id: int = None) -> HttpResponse:
perm_form = SettingAlertUserForm()
form_method = 'post'
form_api = 'alert/global'
alert = {}
data = {}

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

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

perm_form_html = perm_form.render(
template_name='forms/snippet.html',
context={'form': perm_form, 'existing': alert},
context={'form': perm_form, 'existing': data},
)
return render(
request, status=200, template_name='settings/alert_edit.html',
Expand Down

0 comments on commit f15ce99

Please sign in to comment.