mirror of
https://github.com/ansible/awx.git
synced 2026-05-12 03:47:36 -02:30
Revamp user_capabilities with new copy fields
Add copy fields corresponding to new server-side copying Refactor the way user_capabilities are delivered - move the prefetch definition from views to serializer - store temporary mapping in serializer context - use serializer backlinks to denote polymorphic prefetch model exclusions
This commit is contained in:
@@ -238,6 +238,9 @@ class BaseAccess(object):
|
||||
def can_delete(self, obj):
|
||||
return self.user.is_superuser
|
||||
|
||||
def can_copy(self, obj):
|
||||
return self.can_add({'reference_obj': obj})
|
||||
|
||||
def can_attach(self, obj, sub_obj, relationship, data,
|
||||
skip_sub_obj_read_check=False):
|
||||
if skip_sub_obj_read_check:
|
||||
@@ -333,7 +336,7 @@ class BaseAccess(object):
|
||||
elif "features" not in validation_info:
|
||||
raise LicenseForbids(_("Features not found in active license."))
|
||||
|
||||
def get_user_capabilities(self, obj, method_list=[], parent_obj=None):
|
||||
def get_user_capabilities(self, obj, method_list=[], parent_obj=None, capabilities_cache={}):
|
||||
if obj is None:
|
||||
return {}
|
||||
user_capabilities = {}
|
||||
@@ -356,6 +359,10 @@ class BaseAccess(object):
|
||||
elif display_method == 'copy' and isinstance(obj, WorkflowJobTemplate) and obj.organization_id is None:
|
||||
user_capabilities[display_method] = self.user.is_superuser
|
||||
continue
|
||||
elif display_method == 'copy' and isinstance(obj, Project) and obj.scm_type == '':
|
||||
# Connot copy manual project without errors
|
||||
user_capabilities[display_method] = False
|
||||
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():
|
||||
@@ -370,8 +377,8 @@ class BaseAccess(object):
|
||||
continue
|
||||
|
||||
# Grab the answer from the cache, if available
|
||||
if hasattr(obj, 'capabilities_cache') and display_method in obj.capabilities_cache:
|
||||
user_capabilities[display_method] = obj.capabilities_cache[display_method]
|
||||
if display_method in capabilities_cache:
|
||||
user_capabilities[display_method] = capabilities_cache[display_method]
|
||||
if self.user.is_superuser and not user_capabilities[display_method]:
|
||||
# Cache override for models with bad orphaned state
|
||||
user_capabilities[display_method] = True
|
||||
@@ -389,10 +396,10 @@ class BaseAccess(object):
|
||||
if display_method == 'schedule':
|
||||
user_capabilities['schedule'] = user_capabilities['start']
|
||||
continue
|
||||
elif display_method == 'delete' and not isinstance(obj, (User, UnifiedJob)):
|
||||
elif display_method == 'delete' and not isinstance(obj, (User, UnifiedJob, CustomInventoryScript)):
|
||||
user_capabilities['delete'] = user_capabilities['edit']
|
||||
continue
|
||||
elif display_method == 'copy' and isinstance(obj, (Group, Host)):
|
||||
elif display_method == 'copy' and isinstance(obj, (Group, Host, CustomInventoryScript)):
|
||||
user_capabilities['copy'] = user_capabilities['edit']
|
||||
continue
|
||||
|
||||
@@ -1316,9 +1323,6 @@ class JobTemplateAccess(BaseAccess):
|
||||
else:
|
||||
return False
|
||||
|
||||
def can_copy(self, obj):
|
||||
return self.can_add({'reference_obj': obj})
|
||||
|
||||
def can_start(self, obj, validate_license=True):
|
||||
# Check license.
|
||||
if validate_license:
|
||||
|
||||
@@ -180,12 +180,6 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
|
||||
else:
|
||||
return super(UnifiedJobTemplate, self).unique_error_message(model_class, unique_check)
|
||||
|
||||
@classmethod
|
||||
def invalid_user_capabilities_prefetch_models(cls):
|
||||
if cls != UnifiedJobTemplate:
|
||||
return []
|
||||
return ['project', 'inventorysource', 'systemjobtemplate']
|
||||
|
||||
@classmethod
|
||||
def _submodels_with_roles(cls):
|
||||
ujt_classes = [c for c in cls.__subclasses__()
|
||||
|
||||
@@ -32,6 +32,7 @@ from django.db import DatabaseError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models.fields.related import ForeignObjectRel, ManyToManyField
|
||||
from django.db.models.query import QuerySet
|
||||
from django.db.models import Q
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import ParseError, PermissionDenied
|
||||
@@ -44,7 +45,7 @@ logger = logging.getLogger('awx.main.utils')
|
||||
__all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore', 'memoize', 'memoize_delete',
|
||||
'get_ansible_version', 'get_ssh_version', 'get_licenser', 'get_awx_version', 'update_scm_url',
|
||||
'get_type_for_model', 'get_model_for_type', 'copy_model_by_class',
|
||||
'copy_m2m_relationships', 'cache_list_capabilities', 'to_python_boolean',
|
||||
'copy_m2m_relationships', 'prefetch_page_capabilities', 'to_python_boolean',
|
||||
'ignore_inventory_computed_fields', 'ignore_inventory_group_removal',
|
||||
'_inventory_updates', 'get_pk_from_dict', 'getattrd', 'NoDefaultProvided',
|
||||
'get_current_apps', 'set_current_apps', 'OutputEventFilter',
|
||||
@@ -503,7 +504,6 @@ def get_model_for_type(type):
|
||||
'''
|
||||
Return model class for a given type name.
|
||||
'''
|
||||
from django.db.models import Q
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
for ct in ContentType.objects.filter(Q(app_label='main') | Q(app_label='auth', model='user')):
|
||||
ct_model = ct.model_class()
|
||||
@@ -516,30 +516,31 @@ def get_model_for_type(type):
|
||||
raise DatabaseError('"{}" is not a valid AWX model.'.format(type))
|
||||
|
||||
|
||||
def cache_list_capabilities(page, prefetch_list, model, user):
|
||||
def prefetch_page_capabilities(model, page, prefetch_list, user):
|
||||
'''
|
||||
Given a `page` list of objects, the specified roles for the specified user
|
||||
are save on each object in the list, using 1 query for each role type
|
||||
Given a `page` list of objects, a nested dictionary of user_capabilities
|
||||
are returned by id, ex.
|
||||
{
|
||||
4: {'edit': True, 'start': True},
|
||||
6: {'edit': False, 'start': False}
|
||||
}
|
||||
Each capability is produced for all items in the page in a single query
|
||||
|
||||
Examples:
|
||||
capabilities_prefetch = ['admin', 'execute']
|
||||
Examples of prefetch language:
|
||||
prefetch_list = ['admin', 'execute']
|
||||
--> prefetch the admin (edit) and execute (start) permissions for
|
||||
items in list for current user
|
||||
capabilities_prefetch = ['inventory.admin']
|
||||
prefetch_list = ['inventory.admin']
|
||||
--> prefetch the related inventory FK permissions for current user,
|
||||
and put it into the object's cache
|
||||
capabilities_prefetch = [{'copy': ['inventory.admin', 'project.admin']}]
|
||||
prefetch_list = [{'copy': ['inventory.admin', 'project.admin']}]
|
||||
--> prefetch logical combination of admin permission to inventory AND
|
||||
project, put into cache dictionary as "copy"
|
||||
'''
|
||||
from django.db.models import Q
|
||||
page_ids = [obj.id for obj in page]
|
||||
mapping = {}
|
||||
for obj in page:
|
||||
obj.capabilities_cache = {}
|
||||
|
||||
skip_models = []
|
||||
if hasattr(model, 'invalid_user_capabilities_prefetch_models'):
|
||||
skip_models = model.invalid_user_capabilities_prefetch_models()
|
||||
mapping[obj.id] = {}
|
||||
|
||||
for prefetch_entry in prefetch_list:
|
||||
|
||||
@@ -583,11 +584,9 @@ def cache_list_capabilities(page, prefetch_list, model, user):
|
||||
|
||||
# Save data item-by-item
|
||||
for obj in page:
|
||||
if skip_models and obj.__class__.__name__.lower() in skip_models:
|
||||
continue
|
||||
obj.capabilities_cache[display_method] = False
|
||||
if obj.pk in ids_with_role:
|
||||
obj.capabilities_cache[display_method] = True
|
||||
mapping[obj.pk][display_method] = bool(obj.pk in ids_with_role)
|
||||
|
||||
return mapping
|
||||
|
||||
|
||||
def validate_vars_type(vars_obj):
|
||||
|
||||
Reference in New Issue
Block a user