mirror of
https://github.com/ansible/awx.git
synced 2026-03-02 09:18:48 -03:30
Merge pull request #6029 from AlanCoding/group_v1
Group serializer special v1 functionality
This commit is contained in:
@@ -1238,13 +1238,41 @@ class HostSerializer(BaseSerializerWithVariables):
|
|||||||
|
|
||||||
|
|
||||||
class GroupSerializer(BaseSerializerWithVariables):
|
class GroupSerializer(BaseSerializerWithVariables):
|
||||||
show_capabilities = ['copy', 'edit', 'delete']
|
inventory_source = serializers.SerializerMethodField(
|
||||||
|
help_text=_('Dedicated inventory source for the group, will be removed in 3.3.'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
fields = ('*', 'inventory', 'variables', 'has_active_failures',
|
fields = ('*', 'inventory', 'variables', 'has_active_failures',
|
||||||
'total_hosts', 'hosts_with_active_failures', 'total_groups',
|
'total_hosts', 'hosts_with_active_failures', 'total_groups',
|
||||||
'groups_with_active_failures', 'has_inventory_sources')
|
'groups_with_active_failures', 'has_inventory_sources', 'inventory_source')
|
||||||
|
|
||||||
|
def get_fields(self): # TODO: remove in 3.3
|
||||||
|
fields = super(GroupSerializer, self).get_fields()
|
||||||
|
if not self.V1:
|
||||||
|
fields.pop('inventory_source')
|
||||||
|
return fields
|
||||||
|
|
||||||
|
@property
|
||||||
|
def V1(self):
|
||||||
|
request = self.context.get('request')
|
||||||
|
# TODO: use the better version-getter after merged with other branches
|
||||||
|
if request and request.version == 'v1':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def show_capabilities(self): # TODO: consolidate in 3.3
|
||||||
|
if self.V1:
|
||||||
|
return ['copy', 'edit', 'start', 'schedule', 'delete']
|
||||||
|
else:
|
||||||
|
return ['copy', 'edit', 'delete']
|
||||||
|
|
||||||
|
def get_inventory_source(self, obj): # TODO: remove in 3.3
|
||||||
|
try:
|
||||||
|
return obj.deprecated_inventory_source.id
|
||||||
|
except Group.deprecated_inventory_source.RelatedObjectDoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
def build_relational_field(self, field_name, relation_info):
|
def build_relational_field(self, field_name, relation_info):
|
||||||
field_class, field_kwargs = super(GroupSerializer, self).build_relational_field(field_name, relation_info)
|
field_class, field_kwargs = super(GroupSerializer, self).build_relational_field(field_name, relation_info)
|
||||||
@@ -1268,10 +1296,22 @@ class GroupSerializer(BaseSerializerWithVariables):
|
|||||||
inventory_sources = self.reverse('api:group_inventory_sources_list', kwargs={'pk': obj.pk}),
|
inventory_sources = self.reverse('api:group_inventory_sources_list', kwargs={'pk': obj.pk}),
|
||||||
ad_hoc_commands = self.reverse('api:group_ad_hoc_commands_list', kwargs={'pk': obj.pk}),
|
ad_hoc_commands = self.reverse('api:group_ad_hoc_commands_list', kwargs={'pk': obj.pk}),
|
||||||
))
|
))
|
||||||
|
if self.V1: # TODO: remove in 3.3
|
||||||
|
try:
|
||||||
|
res['inventory_source'] = self.reverse('api:inventory_source_detail',
|
||||||
|
kwargs={'pk': obj.deprecated_inventory_source.pk})
|
||||||
|
except Group.deprecated_inventory_source.RelatedObjectDoesNotExist:
|
||||||
|
res['inventory_source'] = None
|
||||||
if obj.inventory:
|
if obj.inventory:
|
||||||
res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
|
res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def create(self, validated_data): # TODO: remove in 3.3
|
||||||
|
instance = super(GroupSerializer, self).create(validated_data)
|
||||||
|
if self.V1:
|
||||||
|
InventorySource.objects.create(deprecated_group=instance, inventory=instance.inventory)
|
||||||
|
return instance
|
||||||
|
|
||||||
def validate_name(self, value):
|
def validate_name(self, value):
|
||||||
if value in ('all', '_meta'):
|
if value in ('all', '_meta'):
|
||||||
raise serializers.ValidationError(_('Invalid group name.'))
|
raise serializers.ValidationError(_('Invalid group name.'))
|
||||||
@@ -1428,11 +1468,13 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
|||||||
last_update_failed = serializers.BooleanField(read_only=True)
|
last_update_failed = serializers.BooleanField(read_only=True)
|
||||||
last_updated = serializers.DateTimeField(read_only=True)
|
last_updated = serializers.DateTimeField(read_only=True)
|
||||||
show_capabilities = ['start', 'schedule', 'edit', 'delete']
|
show_capabilities = ['start', 'schedule', 'edit', 'delete']
|
||||||
|
group = serializers.SerializerMethodField(
|
||||||
|
help_text=_('Automatic group relationship, will be removed in 3.3'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventorySource
|
model = InventorySource
|
||||||
fields = ('*', 'name', 'inventory', 'update_on_launch', 'update_cache_timeout') + \
|
fields = ('*', 'name', 'inventory', 'update_on_launch', 'update_cache_timeout') + \
|
||||||
('last_update_failed', 'last_updated') # Backwards compatibility.
|
('last_update_failed', 'last_updated', 'group') # Backwards compatibility.
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(InventorySourceSerializer, self).get_related(obj)
|
res = super(InventorySourceSerializer, self).get_related(obj)
|
||||||
@@ -1456,8 +1498,30 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
|||||||
if obj.last_update:
|
if obj.last_update:
|
||||||
res['last_update'] = self.reverse('api:inventory_update_detail',
|
res['last_update'] = self.reverse('api:inventory_update_detail',
|
||||||
kwargs={'pk': obj.last_update.pk})
|
kwargs={'pk': obj.last_update.pk})
|
||||||
|
if self.V1: # TODO: remove in 3.3
|
||||||
|
res['group'] = None
|
||||||
|
if obj.deprecated_group:
|
||||||
|
res['group'] = self.reverse('api:group_detail', kwargs={'pk': obj.deprecated_group.pk})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def get_fields(self): # TODO: remove in 3.3
|
||||||
|
fields = super(InventorySourceSerializer, self).get_fields()
|
||||||
|
if not self.V1:
|
||||||
|
fields.pop('group')
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_group(self, obj): # TODO: remove in 3.3
|
||||||
|
if obj.deprecated_group:
|
||||||
|
return obj.deprecated_group.id
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def V1(self): # TODO: use the better version-getter after merged with other branches
|
||||||
|
request = self.context.get('request')
|
||||||
|
if request and request.version == 'v1':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def to_representation(self, obj):
|
def to_representation(self, obj):
|
||||||
ret = super(InventorySourceSerializer, self).to_representation(obj)
|
ret = super(InventorySourceSerializer, self).to_representation(obj)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
|
|||||||
@@ -1855,7 +1855,7 @@ class GroupList(ListCreateAPIView):
|
|||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
serializer_class = GroupSerializer
|
serializer_class = GroupSerializer
|
||||||
capabilities_prefetch = ['inventory.admin', 'inventory.adhoc', 'inventory.update']
|
capabilities_prefetch = ['inventory.admin', 'inventory.adhoc']
|
||||||
|
|
||||||
|
|
||||||
class EnforceParentRelationshipMixin(object):
|
class EnforceParentRelationshipMixin(object):
|
||||||
@@ -1999,6 +1999,11 @@ class GroupDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
if not request.user.can_access(self.model, 'delete', obj):
|
if not request.user.can_access(self.model, 'delete', obj):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
if self.request.version == 'v1': # TODO: deletion of automatic inventory_source, remove in 3.3
|
||||||
|
try:
|
||||||
|
obj.deprecated_inventory_source.delete()
|
||||||
|
except Group.deprecated_inventory_source.RelatedObjectDoesNotExist:
|
||||||
|
pass
|
||||||
obj.delete_recursive()
|
obj.delete_recursive()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
@@ -2189,7 +2194,7 @@ class InventorySourceList(ListAPIView):
|
|||||||
new_in_14 = True
|
new_in_14 = True
|
||||||
|
|
||||||
|
|
||||||
class InventorySourceDetail(RetrieveUpdateAPIView):
|
class InventorySourceDetail(RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
model = InventorySource
|
model = InventorySource
|
||||||
serializer_class = InventorySourceSerializer
|
serializer_class = InventorySourceSerializer
|
||||||
|
|||||||
@@ -346,6 +346,17 @@ class BaseAccess(object):
|
|||||||
elif display_method == 'copy' and isinstance(obj, WorkflowJobTemplate) and obj.organization_id is None:
|
elif display_method == 'copy' and isinstance(obj, WorkflowJobTemplate) and obj.organization_id is None:
|
||||||
user_capabilities[display_method] = self.user.is_superuser
|
user_capabilities[display_method] = self.user.is_superuser
|
||||||
continue
|
continue
|
||||||
|
elif display_method in ['start', 'schedule'] and isinstance(obj, Group): # TODO: remove in 3.3
|
||||||
|
try:
|
||||||
|
if obj.deprecated_inventory_source and not obj.deprecated_inventory_source._can_update():
|
||||||
|
user_capabilities[display_method] = False
|
||||||
|
continue
|
||||||
|
except Group.deprecated_inventory_source.RelatedObjectDoesNotExist:
|
||||||
|
user_capabilities[display_method] = False
|
||||||
|
continue
|
||||||
|
if obj.inventory_source and not obj.inventory_source._can_update():
|
||||||
|
user_capabilities[display_method] = False
|
||||||
|
continue
|
||||||
elif display_method in ['start', 'schedule'] and isinstance(obj, (Project)):
|
elif display_method in ['start', 'schedule'] and isinstance(obj, (Project)):
|
||||||
if obj.scm_type == '':
|
if obj.scm_type == '':
|
||||||
user_capabilities[display_method] = False
|
user_capabilities[display_method] = False
|
||||||
@@ -720,6 +731,19 @@ class GroupAccess(BaseAccess):
|
|||||||
"active_jobs": active_jobs})
|
"active_jobs": active_jobs})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def can_start(self, obj, validate_license=True):
|
||||||
|
# TODO: Delete for 3.3, only used by v1 serializer
|
||||||
|
# Used as another alias to inventory_source start access for user_capabilities
|
||||||
|
if obj:
|
||||||
|
try:
|
||||||
|
return self.user.can_access(
|
||||||
|
InventorySource, 'start', obj.deprecated_inventory_source,
|
||||||
|
validate_license=validate_license)
|
||||||
|
obj.deprecated_inventory_source
|
||||||
|
except Group.deprecated_inventory_source.RelatedObjectDoesNotExist:
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class InventorySourceAccess(BaseAccess):
|
class InventorySourceAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='inventorysource',
|
model_name='inventorysource',
|
||||||
name='deprecated_group',
|
name='deprecated_group',
|
||||||
field=models.ForeignKey(related_name='deprecated_inventory_source', default=None, null=True, to='main.Group'),
|
field=models.OneToOneField(related_name='deprecated_inventory_source', null=True, default=None, to='main.Group'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='inventorysource',
|
model_name='inventorysource',
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ class Migration(migrations.Migration):
|
|||||||
operations = [
|
operations = [
|
||||||
# Inventory Refresh
|
# Inventory Refresh
|
||||||
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
||||||
migrations.RunPython(invsrc.remove_manual_inventory_sources),
|
|
||||||
migrations.RunPython(invsrc.remove_inventory_source_with_no_inventory_link),
|
migrations.RunPython(invsrc.remove_inventory_source_with_no_inventory_link),
|
||||||
migrations.RunPython(invsrc.rename_inventory_sources),
|
migrations.RunPython(invsrc.rename_inventory_sources),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ def remove_manual_inventory_sources(apps, schema_editor):
|
|||||||
Group creation and we would use the parent Group as our interface for the user.
|
Group creation and we would use the parent Group as our interface for the user.
|
||||||
During that process we would create InventorySource that had a source of "manual".
|
During that process we would create InventorySource that had a source of "manual".
|
||||||
'''
|
'''
|
||||||
|
# TODO: use this in the 3.3 data migrations
|
||||||
InventorySource = apps.get_model('main', 'InventorySource')
|
InventorySource = apps.get_model('main', 'InventorySource')
|
||||||
# see models/inventory.py SOURCE_CHOICES - ('', _('Manual'))
|
# see models/inventory.py SOURCE_CHOICES - ('', _('Manual'))
|
||||||
logger.debug("Removing all Manual InventorySource from database.")
|
logger.debug("Removing all Manual InventorySource from database.")
|
||||||
|
|||||||
@@ -1063,7 +1063,7 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
|
|
||||||
deprecated_group = models.ForeignKey(
|
deprecated_group = models.OneToOneField(
|
||||||
'Group',
|
'Group',
|
||||||
related_name='deprecated_inventory_source',
|
related_name='deprecated_inventory_source',
|
||||||
null=True,
|
null=True,
|
||||||
@@ -1178,6 +1178,16 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
|
|||||||
success=list(success_notification_templates),
|
success=list(success_notification_templates),
|
||||||
any=list(any_notification_templates))
|
any=list(any_notification_templates))
|
||||||
|
|
||||||
|
def clean_source(self): # TODO: remove in 3.3
|
||||||
|
source = self.source
|
||||||
|
if source and self.deprecated_group:
|
||||||
|
qs = self.deprecated_group.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES)
|
||||||
|
existing_sources = qs.exclude(pk=self.pk)
|
||||||
|
if existing_sources.count():
|
||||||
|
s = u', '.join([x.deprecated_group.name for x in existing_sources])
|
||||||
|
raise ValidationError(_('Unable to configure this item for cloud sync. It is already managed by %s.') % s)
|
||||||
|
return source
|
||||||
|
|
||||||
|
|
||||||
class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
|
class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
|
||||||
'''
|
'''
|
||||||
@@ -1212,6 +1222,8 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
|
|||||||
|
|
||||||
def websocket_emit_data(self):
|
def websocket_emit_data(self):
|
||||||
websocket_data = super(InventoryUpdate, self).websocket_emit_data()
|
websocket_data = super(InventoryUpdate, self).websocket_emit_data()
|
||||||
|
if self.inventory_source.deprecated_group is not None: # TODO: remove in 3.3
|
||||||
|
websocket_data.update(dict(group_id=self.inventory_source.deprecated_group.id))
|
||||||
return websocket_data
|
return websocket_data
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -372,6 +372,10 @@ model_serializer_mapping = {
|
|||||||
|
|
||||||
def activity_stream_create(sender, instance, created, **kwargs):
|
def activity_stream_create(sender, instance, created, **kwargs):
|
||||||
if created and activity_stream_enabled:
|
if created and activity_stream_enabled:
|
||||||
|
# TODO: remove deprecated_group conditional in 3.3
|
||||||
|
# Skip recording any inventory source directly associated with a group.
|
||||||
|
if isinstance(instance, InventorySource) and instance.deprecated_group:
|
||||||
|
return
|
||||||
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
||||||
changes = model_to_dict(instance, model_serializer_mapping)
|
changes = model_to_dict(instance, model_serializer_mapping)
|
||||||
# Special case where Job survey password variables need to be hidden
|
# Special case where Job survey password variables need to be hidden
|
||||||
@@ -417,6 +421,10 @@ def activity_stream_update(sender, instance, **kwargs):
|
|||||||
def activity_stream_delete(sender, instance, **kwargs):
|
def activity_stream_delete(sender, instance, **kwargs):
|
||||||
if not activity_stream_enabled:
|
if not activity_stream_enabled:
|
||||||
return
|
return
|
||||||
|
# TODO: remove deprecated_group conditional in 3.3
|
||||||
|
# Skip recording any inventory source directly associated with a group.
|
||||||
|
if isinstance(instance, InventorySource) and instance.deprecated_group:
|
||||||
|
return
|
||||||
changes = model_to_dict(instance)
|
changes = model_to_dict(instance)
|
||||||
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
||||||
activity_entry = ActivityStream(
|
activity_entry = ActivityStream(
|
||||||
|
|||||||
Reference in New Issue
Block a user