Merge pull request #6029 from AlanCoding/group_v1

Group serializer special v1 functionality
This commit is contained in:
Alan Rominger
2017-04-17 11:37:26 -04:00
committed by GitHub
8 changed files with 121 additions and 8 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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):
''' '''

View File

@@ -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',

View File

@@ -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),
] ]

View File

@@ -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.")

View File

@@ -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):

View File

@@ -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(