awx/awx/main/fields.py
2016-02-10 15:13:24 -05:00

199 lines
7.5 KiB
Python

# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Django
from django.db.models.signals import post_save
from django.db import models
from django.db.models.fields.related import SingleRelatedObjectDescriptor
from django.db.models.fields.related import ReverseSingleRelatedObjectDescriptor
from django.core.exceptions import FieldError
# AWX
from awx.main.models.rbac import Resource, RolePermission, Role
__all__ = ['AutoOneToOneField', 'ImplicitResourceField', 'ImplicitRoleField']
# Based on AutoOneToOneField from django-annoying:
# https://bitbucket.org/offline/django-annoying/src/a0de8b294db3/annoying/fields.py
class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
"""Descriptor for access to the object from its related class."""
def __get__(self, instance, instance_type=None):
try:
return super(AutoSingleRelatedObjectDescriptor,
self).__get__(instance, instance_type)
except self.related.related_model.DoesNotExist:
obj = self.related.related_model(**{self.related.field.name: instance})
if self.related.field.rel.parent_link:
raise NotImplementedError('not supported with polymorphic!')
for f in instance._meta.local_fields:
setattr(obj, f.name, getattr(instance, f.name))
obj.save()
return obj
class AutoOneToOneField(models.OneToOneField):
"""OneToOneField that creates related object if it doesn't exist."""
def contribute_to_related_class(self, cls, related):
setattr(cls, related.get_accessor_name(),
AutoSingleRelatedObjectDescriptor(related))
def resolve_field(obj, field):
for f in field.split('.'):
if hasattr(obj, f):
obj = getattr(obj, f)
else:
obj = None
return obj
class ResourceFieldDescriptor(ReverseSingleRelatedObjectDescriptor):
"""Descriptor for access to the object from its related class."""
def __init__(self, parent_resource, *args, **kwargs):
self.parent_resource = parent_resource
super(ResourceFieldDescriptor, self).__init__(*args, **kwargs)
def __get__(self, instance, instance_type=None):
resource = super(ResourceFieldDescriptor, self).__get__(instance, instance_type)
if resource:
return resource
resource = Resource._default_manager.create()
if self.parent_resource:
# Take first non null parent resource
parent = None
if type(self.parent_resource) is list:
for path in self.parent_resource:
parent = resolve_field(instance, path)
if parent:
break
else:
parent = resolve_field(instance, self.parent_resource)
resource.parent = parent
resource.save()
setattr(instance, self.field.name, resource)
instance.save(update_fields=[self.field.name,])
return resource
class ImplicitResourceField(models.ForeignKey):
"""Creates an associated resource object if one doesn't already exist"""
def __init__(self, parent_resource=None, *args, **kwargs):
self.parent_resource = parent_resource
kwargs.setdefault('to', 'Resource')
kwargs.setdefault('related_name', '+')
kwargs.setdefault('null', 'True')
super(ImplicitResourceField, self).__init__(*args, **kwargs)
def contribute_to_class(self, cls, name):
super(ImplicitResourceField, self).contribute_to_class(cls, name)
setattr(cls, self.name, ResourceFieldDescriptor(self.parent_resource, self))
post_save.connect(self._save, cls, True)
def _save(self, instance, *args, **kwargs):
# Ensure that our field gets initialized after our first save
getattr(instance, self.name)
class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
"""Descriptor Implict Role Fields. Auto-creates the appropriate role entry on first access"""
def __init__(self, role_name, resource_field, permissions, parent_role, *args, **kwargs):
self.role_name = role_name
self.resource_field = resource_field
self.permissions = permissions
self.parent_role = parent_role
super(ImplicitRoleDescriptor, self).__init__(*args, **kwargs)
def __get__(self, instance, instance_type=None):
role = super(ImplicitRoleDescriptor, self).__get__(instance, instance_type)
if role:
return role
if not self.role_name:
raise FieldError('Implicit role missing `role_name`')
role = Role._default_manager.create(name=self.role_name)
if self.parent_role:
# Add all non-null parent roles as parents
if type(self.parent_role) is list:
for path in self.parent_role:
if path.startswith("singleton:"):
parent = Role.singleton(path[10:])
else:
parent = resolve_field(instance, path)
if parent:
role.parents.add(parent)
else:
if self.parent_role.startswith("singleton:"):
parent = Role.singleton(self.parent_role[10:])
else:
parent = resolve_field(instance, self.parent_role)
if parent:
role.parents.add(parent)
setattr(instance, self.field.name, role)
instance.save(update_fields=[self.field.name,])
if self.resource_field and self.permissions:
permissions = RolePermission(
role=role,
resource=getattr(instance, self.resource_field)
)
if 'all' in self.permissions and self.permissions['all']:
del self.permissions['all']
self.permissions['create'] = True
self.permissions['read'] = True
self.permissions['write'] = True
self.permissions['update'] = True
self.permissions['delete'] = True
self.permissions['scm_update'] = True
self.permissions['use'] = True
self.permissions['execute'] = True
for k,v in self.permissions.items():
setattr(permissions, k, v)
permissions.save()
return role
class ImplicitRoleField(models.ForeignKey):
"""Implicitly creates a role entry for a resource"""
def __init__(self, role_name=None, resource_field=None, permissions=None, parent_role=None, *args, **kwargs):
self.role_name = role_name
self.resource_field = resource_field
self.permissions = permissions
self.parent_role = parent_role
kwargs.setdefault('to', 'Role')
kwargs.setdefault('related_name', '+')
kwargs.setdefault('null', 'True')
super(ImplicitRoleField, self).__init__(*args, **kwargs)
def contribute_to_class(self, cls, name):
super(ImplicitRoleField, self).contribute_to_class(cls, name)
setattr(cls,
self.name,
ImplicitRoleDescriptor(
self.role_name,
self.resource_field,
self.permissions,
self.parent_role,
self
)
)
post_save.connect(self._save, cls, True)
def _save(self, instance, *args, **kwargs):
# Ensure that our field gets initialized after our first save
getattr(instance, self.name)