mirror of
https://github.com/ansible/awx.git
synced 2026-03-18 01:17:35 -02:30
Complete consolidation of the label views
This commit is contained in:
@@ -63,7 +63,6 @@ __all__ = [
|
|||||||
'SubDetailAPIView',
|
'SubDetailAPIView',
|
||||||
'ResourceAccessList',
|
'ResourceAccessList',
|
||||||
'ParentMixin',
|
'ParentMixin',
|
||||||
'DeleteLastUnattachLabelMixin',
|
|
||||||
'SubListAttachDetachAPIView',
|
'SubListAttachDetachAPIView',
|
||||||
'CopyAPIView',
|
'CopyAPIView',
|
||||||
'BaseUsersList',
|
'BaseUsersList',
|
||||||
@@ -775,28 +774,6 @@ class SubListAttachDetachAPIView(SubListCreateAttachDetachAPIView):
|
|||||||
return {'id': None}
|
return {'id': None}
|
||||||
|
|
||||||
|
|
||||||
class DeleteLastUnattachLabelMixin(object):
|
|
||||||
"""
|
|
||||||
Models for which you want the last instance to be deleted from the database
|
|
||||||
when the last disassociate is called should inherit from this class. Further,
|
|
||||||
the model should implement is_detached()
|
|
||||||
"""
|
|
||||||
|
|
||||||
def unattach(self, request, *args, **kwargs):
|
|
||||||
(sub_id, res) = super(DeleteLastUnattachLabelMixin, self).unattach_validate(request)
|
|
||||||
if res:
|
|
||||||
return res
|
|
||||||
|
|
||||||
res = super(DeleteLastUnattachLabelMixin, self).unattach_by_id(request, sub_id)
|
|
||||||
|
|
||||||
obj = self.model.objects.get(id=sub_id)
|
|
||||||
|
|
||||||
if obj.is_detached():
|
|
||||||
obj.delete()
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
class SubDetailAPIView(ParentMixin, generics.RetrieveAPIView, GenericAPIView):
|
class SubDetailAPIView(ParentMixin, generics.RetrieveAPIView, GenericAPIView):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import LabelList, LabelDetail
|
from awx.api.views.labels import LabelList, LabelDetail
|
||||||
|
|
||||||
|
|
||||||
urls = [re_path(r'^$', LabelList.as_view(), name='label_list'), re_path(r'^(?P<pk>[0-9]+)/$', LabelDetail.as_view(), name='label_detail')]
|
urls = [re_path(r'^$', LabelList.as_view(), name='label_list'), re_path(r'^(?P<pk>[0-9]+)/$', LabelDetail.as_view(), name='label_detail')]
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ from awx.api.generics import (
|
|||||||
APIView,
|
APIView,
|
||||||
BaseUsersList,
|
BaseUsersList,
|
||||||
CopyAPIView,
|
CopyAPIView,
|
||||||
DeleteLastUnattachLabelMixin,
|
|
||||||
GenericAPIView,
|
GenericAPIView,
|
||||||
ListAPIView,
|
ListAPIView,
|
||||||
ListCreateAPIView,
|
ListCreateAPIView,
|
||||||
@@ -86,6 +85,7 @@ from awx.api.generics import (
|
|||||||
SubListCreateAttachDetachAPIView,
|
SubListCreateAttachDetachAPIView,
|
||||||
SubListDestroyAPIView,
|
SubListDestroyAPIView,
|
||||||
)
|
)
|
||||||
|
from awx.api.views.labels import LabelSubListCreateAttachDetachView
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
from awx.main import models
|
from awx.main import models
|
||||||
from awx.main.utils import (
|
from awx.main.utils import (
|
||||||
@@ -209,26 +209,6 @@ def api_exception_handler(exc, context):
|
|||||||
return exception_handler(exc, context)
|
return exception_handler(exc, context)
|
||||||
|
|
||||||
|
|
||||||
class LabelList(DeleteLastUnattachLabelMixin, SubListCreateAttachDetachAPIView):
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
# If a label already exists in the database, attach it instead of erroring out
|
|
||||||
# that it already exists
|
|
||||||
if not getattr(self, 'label_filter', None):
|
|
||||||
return Response(dict(msg=_('Class {} missing label filter.'.format(self.__class__.__name__))), status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
if 'id' not in request.data and 'name' in request.data and 'organization' in request.data:
|
|
||||||
existing = models.Label.objects.filter(name=request.data['name'], organization_id=request.data['organization'])
|
|
||||||
if existing.exists():
|
|
||||||
existing = existing[0]
|
|
||||||
request.data['id'] = existing.id
|
|
||||||
del request.data['name']
|
|
||||||
del request.data['organization']
|
|
||||||
if models.Label.objects.filter(**{self.label_filter: self.kwargs['pk']}).count() > 100:
|
|
||||||
return Response(
|
|
||||||
dict(msg=_('Maximum number of labels for {} reached.'.format(self.parent_model._meta.verbose_name_raw))), status=status.HTTP_400_BAD_REQUEST
|
|
||||||
)
|
|
||||||
return super().post(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class DashboardView(APIView):
|
class DashboardView(APIView):
|
||||||
|
|
||||||
deprecated = True
|
deprecated = True
|
||||||
@@ -638,13 +618,9 @@ class ScheduleCredentialsList(LaunchConfigCredentialsBase):
|
|||||||
parent_model = models.Schedule
|
parent_model = models.Schedule
|
||||||
|
|
||||||
|
|
||||||
class ScheduleLabelsList(LabelList):
|
class ScheduleLabelsList(LabelSubListCreateAttachDetachView):
|
||||||
|
|
||||||
model = models.Label
|
|
||||||
serializer_class = serializers.LabelSerializer
|
|
||||||
parent_model = models.Schedule
|
parent_model = models.Schedule
|
||||||
relationship = 'labels'
|
|
||||||
label_filter = 'schedule_labels'
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleInstanceGroupList(SubListAttachDetachAPIView):
|
class ScheduleInstanceGroupList(SubListAttachDetachAPIView):
|
||||||
@@ -2757,13 +2733,9 @@ class JobTemplateCredentialsList(SubListCreateAttachDetachAPIView):
|
|||||||
return super(JobTemplateCredentialsList, self).is_valid_relation(parent, sub, created)
|
return super(JobTemplateCredentialsList, self).is_valid_relation(parent, sub, created)
|
||||||
|
|
||||||
|
|
||||||
class JobTemplateLabelList(LabelList):
|
class JobTemplateLabelList(LabelSubListCreateAttachDetachView):
|
||||||
|
|
||||||
model = models.Label
|
|
||||||
serializer_class = serializers.LabelSerializer
|
|
||||||
parent_model = models.JobTemplate
|
parent_model = models.JobTemplate
|
||||||
relationship = 'labels'
|
|
||||||
label_filter = 'unifiedjobtemplate_labels'
|
|
||||||
|
|
||||||
|
|
||||||
class JobTemplateCallback(GenericAPIView):
|
class JobTemplateCallback(GenericAPIView):
|
||||||
@@ -2989,13 +2961,12 @@ class WorkflowJobNodeCredentialsList(SubListAPIView):
|
|||||||
relationship = 'credentials'
|
relationship = 'credentials'
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobNodeLabelsList(LabelList):
|
class WorkflowJobNodeLabelsList(SubListAPIView):
|
||||||
|
|
||||||
model = models.Label
|
model = models.Label
|
||||||
serializer_class = serializers.LabelSerializer
|
serializer_class = serializers.LabelSerializer
|
||||||
parent_model = models.WorkflowJobNode
|
parent_model = models.WorkflowJobNode
|
||||||
relationship = 'labels'
|
relationship = 'labels'
|
||||||
label_filter = 'workflowjobnode_labels'
|
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobNodeInstanceGroupsList(SubListAttachDetachAPIView):
|
class WorkflowJobNodeInstanceGroupsList(SubListAttachDetachAPIView):
|
||||||
@@ -3024,13 +2995,9 @@ class WorkflowJobTemplateNodeCredentialsList(LaunchConfigCredentialsBase):
|
|||||||
parent_model = models.WorkflowJobTemplateNode
|
parent_model = models.WorkflowJobTemplateNode
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobTemplateNodeLabelsList(LabelList):
|
class WorkflowJobTemplateNodeLabelsList(LabelSubListCreateAttachDetachView):
|
||||||
|
|
||||||
model = models.Label
|
|
||||||
serializer_class = serializers.LabelSerializer
|
|
||||||
parent_model = models.WorkflowJobTemplateNode
|
parent_model = models.WorkflowJobTemplateNode
|
||||||
relationship = 'labels'
|
|
||||||
label_filter = 'workflowjobtemplatenode_labels'
|
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobTemplateNodeInstanceGroupsList(SubListAttachDetachAPIView):
|
class WorkflowJobTemplateNodeInstanceGroupsList(SubListAttachDetachAPIView):
|
||||||
@@ -4498,18 +4465,6 @@ class NotificationDetail(RetrieveAPIView):
|
|||||||
serializer_class = serializers.NotificationSerializer
|
serializer_class = serializers.NotificationSerializer
|
||||||
|
|
||||||
|
|
||||||
class LabelList(ListCreateAPIView):
|
|
||||||
|
|
||||||
model = models.Label
|
|
||||||
serializer_class = serializers.LabelSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class LabelDetail(RetrieveUpdateAPIView):
|
|
||||||
|
|
||||||
model = models.Label
|
|
||||||
serializer_class = serializers.LabelSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ActivityStreamList(SimpleListAPIView):
|
class ActivityStreamList(SimpleListAPIView):
|
||||||
|
|
||||||
model = models.ActivityStream
|
model = models.ActivityStream
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ from rest_framework import status
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import ActivityStream, Inventory, JobTemplate, Role, User, InstanceGroup, InventoryUpdateEvent, InventoryUpdate
|
from awx.main.models import ActivityStream, Inventory, JobTemplate, Role, User, InstanceGroup, InventoryUpdateEvent, InventoryUpdate
|
||||||
|
|
||||||
from awx.main.models.label import Label
|
|
||||||
|
|
||||||
from awx.api.generics import (
|
from awx.api.generics import (
|
||||||
ListCreateAPIView,
|
ListCreateAPIView,
|
||||||
RetrieveUpdateDestroyAPIView,
|
RetrieveUpdateDestroyAPIView,
|
||||||
@@ -27,9 +25,8 @@ from awx.api.generics import (
|
|||||||
SubListAttachDetachAPIView,
|
SubListAttachDetachAPIView,
|
||||||
ResourceAccessList,
|
ResourceAccessList,
|
||||||
CopyAPIView,
|
CopyAPIView,
|
||||||
DeleteLastUnattachLabelMixin,
|
|
||||||
SubListCreateAttachDetachAPIView,
|
|
||||||
)
|
)
|
||||||
|
from awx.api.views.labels import LabelSubListCreateAttachDetachView
|
||||||
|
|
||||||
|
|
||||||
from awx.api.serializers import (
|
from awx.api.serializers import (
|
||||||
@@ -39,7 +36,6 @@ from awx.api.serializers import (
|
|||||||
InstanceGroupSerializer,
|
InstanceGroupSerializer,
|
||||||
InventoryUpdateEventSerializer,
|
InventoryUpdateEventSerializer,
|
||||||
JobTemplateSerializer,
|
JobTemplateSerializer,
|
||||||
LabelSerializer,
|
|
||||||
)
|
)
|
||||||
from awx.api.views.mixin import RelatedJobsPreventDeleteMixin
|
from awx.api.views.mixin import RelatedJobsPreventDeleteMixin
|
||||||
|
|
||||||
@@ -157,28 +153,9 @@ class InventoryJobTemplateList(SubListAPIView):
|
|||||||
return qs.filter(inventory=parent)
|
return qs.filter(inventory=parent)
|
||||||
|
|
||||||
|
|
||||||
class InventoryLabelList(DeleteLastUnattachLabelMixin, SubListCreateAttachDetachAPIView, SubListAPIView):
|
class InventoryLabelList(LabelSubListCreateAttachDetachView):
|
||||||
|
|
||||||
model = Label
|
|
||||||
serializer_class = LabelSerializer
|
|
||||||
parent_model = Inventory
|
parent_model = Inventory
|
||||||
relationship = 'labels'
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
# If a label already exists in the database, attach it instead of erroring out
|
|
||||||
# that it already exists
|
|
||||||
if 'id' not in request.data and 'name' in request.data and 'organization' in request.data:
|
|
||||||
existing = Label.objects.filter(name=request.data['name'], organization_id=request.data['organization'])
|
|
||||||
if existing.exists():
|
|
||||||
existing = existing[0]
|
|
||||||
request.data['id'] = existing.id
|
|
||||||
del request.data['name']
|
|
||||||
del request.data['organization']
|
|
||||||
if Label.objects.filter(inventory_labels=self.kwargs['pk']).count() > 100:
|
|
||||||
return Response(
|
|
||||||
dict(msg=_('Maximum number of labels for {} reached.'.format(self.parent_model._meta.verbose_name_raw))), status=status.HTTP_400_BAD_REQUEST
|
|
||||||
)
|
|
||||||
return super(InventoryLabelList, self).post(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryCopy(CopyAPIView):
|
class InventoryCopy(CopyAPIView):
|
||||||
|
|||||||
71
awx/api/views/labels.py
Normal file
71
awx/api/views/labels.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# AWX
|
||||||
|
from awx.api.generics import SubListCreateAttachDetachAPIView, RetrieveUpdateAPIView, ListCreateAPIView
|
||||||
|
from awx.main.models import Label
|
||||||
|
from awx.api.serializers import LabelSerializer
|
||||||
|
|
||||||
|
# Django
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
# Django REST Framework
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.status import HTTP_400_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
|
class LabelSubListCreateAttachDetachView(SubListCreateAttachDetachAPIView):
|
||||||
|
"""
|
||||||
|
For related labels lists like /api/v2/inventories/N/labels/
|
||||||
|
|
||||||
|
We want want the last instance to be deleted from the database
|
||||||
|
when the last disassociate happens.
|
||||||
|
|
||||||
|
Subclasses need to define parent_model
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = Label
|
||||||
|
serializer_class = LabelSerializer
|
||||||
|
relationship = 'labels'
|
||||||
|
|
||||||
|
def unattach(self, request, *args, **kwargs):
|
||||||
|
(sub_id, res) = super().unattach_validate(request)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
|
||||||
|
res = super().unattach_by_id(request, sub_id)
|
||||||
|
|
||||||
|
obj = self.model.objects.get(id=sub_id)
|
||||||
|
|
||||||
|
if obj.is_detached():
|
||||||
|
obj.delete()
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# If a label already exists in the database, attach it instead of erroring out
|
||||||
|
# that it already exists
|
||||||
|
if 'id' not in request.data and 'name' in request.data and 'organization' in request.data:
|
||||||
|
existing = Label.objects.filter(name=request.data['name'], organization_id=request.data['organization'])
|
||||||
|
if existing.exists():
|
||||||
|
existing = existing[0]
|
||||||
|
request.data['id'] = existing.id
|
||||||
|
del request.data['name']
|
||||||
|
del request.data['organization']
|
||||||
|
|
||||||
|
# Give a 400 error if we have attached too many labels to this object
|
||||||
|
label_filter = self.parent_model._meta.get_field(self.relationship).remote_field.name
|
||||||
|
if Label.objects.filter(**{label_filter: self.kwargs['pk']}).count() > 100:
|
||||||
|
return Response(dict(msg=_(f'Maximum number of labels for {self.parent_model._meta.verbose_name_raw} reached.')), status=HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class LabelDetail(RetrieveUpdateAPIView):
|
||||||
|
|
||||||
|
model = Label
|
||||||
|
serializer_class = LabelSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class LabelList(ListCreateAPIView):
|
||||||
|
|
||||||
|
name = _("Labels")
|
||||||
|
model = Label
|
||||||
|
serializer_class = LabelSerializer
|
||||||
@@ -59,7 +59,7 @@ class TestApiRootView:
|
|||||||
|
|
||||||
class TestJobTemplateLabelList:
|
class TestJobTemplateLabelList:
|
||||||
def test_inherited_mixin_unattach(self):
|
def test_inherited_mixin_unattach(self):
|
||||||
with mock.patch('awx.api.generics.DeleteLastUnattachLabelMixin.unattach') as mixin_unattach:
|
with mock.patch('awx.api.views.labels.LabelSubListCreateAttachDetachView.unattach') as mixin_unattach:
|
||||||
view = JobTemplateLabelList()
|
view = JobTemplateLabelList()
|
||||||
mock_request = mock.MagicMock()
|
mock_request = mock.MagicMock()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user