mirror of
https://github.com/ansible/awx.git
synced 2026-05-12 20:07:37 -02:30
@@ -1,6 +1,8 @@
|
|||||||
# Copyright (c) 2015 Ansible, Inc.
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.db.models.signals import (
|
from django.db.models.signals import (
|
||||||
pre_save,
|
pre_save,
|
||||||
@@ -17,10 +19,11 @@ from django.db.models.fields.related import (
|
|||||||
ReverseManyRelatedObjectsDescriptor,
|
ReverseManyRelatedObjectsDescriptor,
|
||||||
)
|
)
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.rbac import RolePermission, Role, batch_role_ancestor_rebuilding
|
from awx.main.models.rbac import batch_role_ancestor_rebuilding
|
||||||
|
from awx.main.utils import get_current_apps
|
||||||
|
|
||||||
__all__ = ['AutoOneToOneField', 'ImplicitRoleField']
|
__all__ = ['AutoOneToOneField', 'ImplicitRoleField']
|
||||||
|
|
||||||
@@ -65,10 +68,14 @@ def resolve_role_field(obj, field):
|
|||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
return []
|
||||||
|
|
||||||
if len(field_components) == 1:
|
if len(field_components) == 1:
|
||||||
if type(obj) is not ImplicitRoleDescriptor and type(obj) is not Role:
|
Role_ = get_current_apps().get_model('main', 'Role')
|
||||||
raise Exception(smart_text('{} refers to a {}, not an ImplicitRoleField or Role'.format(field, type(obj))))
|
if type(obj) is not Role_:
|
||||||
ret.append(obj)
|
raise Exception(smart_text('{} refers to a {}, not a Role'.format(field, type(obj))))
|
||||||
|
ret.append(obj.id)
|
||||||
else:
|
else:
|
||||||
if type(obj) is ManyRelatedObjectsDescriptor:
|
if type(obj) is ManyRelatedObjectsDescriptor:
|
||||||
for o in obj.all():
|
for o in obj.all():
|
||||||
@@ -97,6 +104,14 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
kwargs.setdefault('null', 'True')
|
kwargs.setdefault('null', 'True')
|
||||||
super(ImplicitRoleField, self).__init__(*args, **kwargs)
|
super(ImplicitRoleField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def deconstruct(self):
|
||||||
|
name, path, args, kwargs = super(ImplicitRoleField, self).deconstruct()
|
||||||
|
kwargs['role_name'] = self.role_name
|
||||||
|
kwargs['role_description'] = self.role_description
|
||||||
|
kwargs['permissions'] = self.permissions
|
||||||
|
kwargs['parent_role'] = self.parent_role
|
||||||
|
return name, path, args, kwargs
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
super(ImplicitRoleField, self).contribute_to_class(cls, name)
|
super(ImplicitRoleField, self).contribute_to_class(cls, name)
|
||||||
setattr(cls, self.name, ImplicitRoleDescriptor(self))
|
setattr(cls, self.name, ImplicitRoleDescriptor(self))
|
||||||
@@ -165,8 +180,11 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
def _create_role_instance_if_not_exists(self, instance):
|
def _create_role_instance_if_not_exists(self, instance):
|
||||||
role = getattr(instance, self.name, None)
|
role = getattr(instance, self.name, None)
|
||||||
if role:
|
if role:
|
||||||
return role
|
return
|
||||||
role = Role.objects.create(
|
Role_ = get_current_apps().get_model('main', 'Role')
|
||||||
|
role = Role_.objects.create(
|
||||||
|
created=now(),
|
||||||
|
modified=now(),
|
||||||
name=self.role_name,
|
name=self.role_name,
|
||||||
description=self.role_description
|
description=self.role_description
|
||||||
)
|
)
|
||||||
@@ -178,9 +196,16 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
role.save()
|
role.save()
|
||||||
|
|
||||||
if self.permissions is not None:
|
if self.permissions is not None:
|
||||||
permissions = RolePermission(
|
RolePermission_ = get_current_apps().get_model('main', 'RolePermission')
|
||||||
|
ContentType = get_current_apps().get_model('contenttypes', "ContentType")
|
||||||
|
instance_content_type = ContentType.objects.get_for_model(instance)
|
||||||
|
|
||||||
|
permissions = RolePermission_(
|
||||||
|
created=now(),
|
||||||
|
modified=now(),
|
||||||
role=role,
|
role=role,
|
||||||
resource=instance,
|
content_type=instance_content_type,
|
||||||
|
object_id=instance.id,
|
||||||
auto_generated=True
|
auto_generated=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -203,38 +228,24 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
||||||
implicit_role_field._create_role_instance_if_not_exists(instance)
|
implicit_role_field._create_role_instance_if_not_exists(instance)
|
||||||
|
|
||||||
original_parent_roles = dict()
|
|
||||||
if instance.pk:
|
|
||||||
original = instance.__class__.objects.get(pk=instance.pk)
|
|
||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
|
||||||
original_parent_roles[implicit_role_field.name] = implicit_role_field._resolve_parent_roles(original)
|
|
||||||
|
|
||||||
setattr(instance, '__original_parent_roles', original_parent_roles)
|
|
||||||
|
|
||||||
|
|
||||||
def _post_save(self, instance, created, *args, **kwargs):
|
def _post_save(self, instance, created, *args, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
||||||
implicit_role_field._patch_role_content_object_and_grant_permissions(instance)
|
implicit_role_field._patch_role_content_object_and_grant_permissions(instance)
|
||||||
|
|
||||||
original_parent_roles = getattr(instance, '__original_parent_roles')
|
|
||||||
|
|
||||||
if created:
|
|
||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
|
||||||
original_parent_roles[implicit_role_field.name] = set()
|
|
||||||
|
|
||||||
new_parent_roles = dict()
|
|
||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
|
||||||
new_parent_roles[implicit_role_field.name] = implicit_role_field._resolve_parent_roles(instance)
|
|
||||||
setattr(instance, '__original_parent_roles', new_parent_roles)
|
|
||||||
|
|
||||||
with batch_role_ancestor_rebuilding():
|
with batch_role_ancestor_rebuilding():
|
||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
||||||
cur_role = getattr(instance, implicit_role_field.name)
|
cur_role = getattr(instance, implicit_role_field.name)
|
||||||
original_parents = original_parent_roles[implicit_role_field.name]
|
original_parents = set(json.loads(cur_role.implicit_parents))
|
||||||
new_parents = new_parent_roles[implicit_role_field.name]
|
new_parents = implicit_role_field._resolve_parent_roles(instance)
|
||||||
cur_role.parents.remove(*list(original_parents - new_parents))
|
cur_role.parents.remove(*list(original_parents - new_parents))
|
||||||
cur_role.parents.add(*list(new_parents - original_parents))
|
cur_role.parents.add(*list(new_parents - original_parents))
|
||||||
|
new_parents_list = list(new_parents)
|
||||||
|
new_parents_list.sort()
|
||||||
|
new_parents_json = json.dumps(new_parents_list)
|
||||||
|
if cur_role.implicit_parents != new_parents_json:
|
||||||
|
cur_role.implicit_parents = new_parents_json
|
||||||
|
cur_role.save()
|
||||||
|
|
||||||
|
|
||||||
def _resolve_parent_roles(self, instance):
|
def _resolve_parent_roles(self, instance):
|
||||||
@@ -245,7 +256,18 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
parent_roles = set()
|
parent_roles = set()
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if path.startswith("singleton:"):
|
if path.startswith("singleton:"):
|
||||||
parents = [Role.singleton(path[10:])]
|
singleton_name = path[10:]
|
||||||
|
Role_ = get_current_apps().get_model('main', 'Role')
|
||||||
|
qs = Role_.objects.filter(singleton_name=singleton_name)
|
||||||
|
if qs.count() >= 1:
|
||||||
|
role = qs[0]
|
||||||
|
else:
|
||||||
|
role = Role_.objects.create(created=now(),
|
||||||
|
modified=now(),
|
||||||
|
singleton_name=singleton_name,
|
||||||
|
name=singleton_name,
|
||||||
|
description=singleton_name)
|
||||||
|
parents = [role.id]
|
||||||
else:
|
else:
|
||||||
parents = resolve_role_field(instance, path)
|
parents = resolve_role_field(instance, path)
|
||||||
for parent in parents:
|
for parent in parents:
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from django.conf import settings
|
|||||||
import taggit.managers
|
import taggit.managers
|
||||||
import awx.main.fields
|
import awx.main.fields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -38,6 +37,16 @@ class Migration(migrations.Migration):
|
|||||||
'projects',
|
'projects',
|
||||||
'deprecated_projects',
|
'deprecated_projects',
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='organization',
|
||||||
|
field=models.ForeignKey(related_name='projects', to='main.Organization', blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='team',
|
||||||
|
name='deprecated_projects',
|
||||||
|
field=models.ManyToManyField(related_name='deprecated_teams', to='main.Project', blank=True),
|
||||||
|
),
|
||||||
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Role',
|
name='Role',
|
||||||
@@ -55,6 +64,7 @@ class Migration(migrations.Migration):
|
|||||||
('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)),
|
('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)),
|
||||||
('modified_by', models.ForeignKey(related_name="{u'class': 'role', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
('modified_by', models.ForeignKey(related_name="{u'class': 'role', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||||
('parents', models.ManyToManyField(related_name='children', to='main.Role')),
|
('parents', models.ManyToManyField(related_name='children', to='main.Role')),
|
||||||
|
('implicit_parents', models.ManyToManyField(related_name='implicit_children', to='main.Role')),
|
||||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
@@ -86,146 +96,149 @@ class Migration(migrations.Migration):
|
|||||||
'verbose_name_plural': 'permissions',
|
'verbose_name_plural': 'permissions',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='credential',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Auditor of the credential', parent_role=[b'singleton:System Auditor'], to='main.Role', role_name=b'Credential Auditor', null=b'True', permissions={b'read': True}),
|
||||||
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='credential',
|
model_name='credential',
|
||||||
name='owner_role',
|
name='owner_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Owner of the credential', parent_role=[b'singleton:System Administrator'], to='main.Role', role_name=b'Credential Owner', null=b'True', permissions={b'all': True}),
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='credential',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='credential',
|
model_name='credential',
|
||||||
name='usage_role',
|
name='usage_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this credential, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Credential User', null=b'True', permissions={b'use': True}),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='custominventoryscript',
|
||||||
|
name='admin_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'CustomInventory Administrator', null=b'True', permissions={b'all': True}),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='custominventoryscript',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'CustomInventory Auditor', null=b'True', permissions={b'read': True}),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='custominventoryscript',
|
||||||
|
name='member_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.member_role', to='main.Role', role_name=b'CustomInventory Member', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='group',
|
model_name='group',
|
||||||
name='admin_role',
|
name='admin_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.admin_role', b'parents.admin_role'], to='main.Role', role_name=b'Inventory Group Administrator', null=b'True', permissions={b'all': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='group',
|
model_name='group',
|
||||||
name='auditor_role',
|
name='auditor_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.auditor_role', b'parents.auditor_role'], to='main.Role', role_name=b'Inventory Group Auditor', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='group',
|
model_name='group',
|
||||||
name='executor_role',
|
name='executor_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.executor_role', b'parents.executor_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True', permissions={b'read': True, b'execute': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='group',
|
model_name='group',
|
||||||
name='updater_role',
|
name='updater_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.updater_role', b'parents.updater_role'], to='main.Role', role_name=b'Inventory Group Updater', null=b'True', permissions={b'read': True, b'write': True, b'create': True, b'use': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='inventory',
|
model_name='inventory',
|
||||||
name='admin_role',
|
name='admin_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Inventory Administrator', null=b'True', permissions={b'all': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='inventory',
|
model_name='inventory',
|
||||||
name='auditor_role',
|
name='auditor_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Inventory Auditor', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='inventory',
|
model_name='inventory',
|
||||||
name='executor_role',
|
name='executor_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute jobs against this inventory', parent_role=None, to='main.Role', role_name=b'Inventory Executor', null=b'True', permissions={b'read': True, b'execute': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='inventory',
|
model_name='inventory',
|
||||||
name='updater_role',
|
name='updater_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update the inventory', parent_role=None, to='main.Role', role_name=b'Inventory Updater', null=b'True', permissions={b'read': True, b'update': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='inventory',
|
model_name='inventory',
|
||||||
name='usage_role',
|
name='usage_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this inventory, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Inventory User', null=b'True', permissions={b'use': True}),
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='custominventoryscript',
|
|
||||||
name='admin_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='custominventoryscript',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='custominventoryscript',
|
|
||||||
name='member_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='jobtemplate',
|
model_name='jobtemplate',
|
||||||
name='admin_role',
|
name='admin_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Full access to all settings', parent_role=b'project.admin_role', to='main.Role', role_name=b'Job Template Administrator', null=b'True', permissions={b'all': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='jobtemplate',
|
model_name='jobtemplate',
|
||||||
name='auditor_role',
|
name='auditor_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read-only access to all settings', parent_role=b'project.auditor_role', to='main.Role', role_name=b'Job Template Auditor', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='jobtemplate',
|
model_name='jobtemplate',
|
||||||
name='executor_role',
|
name='executor_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=None, to='main.Role', role_name=b'Job Template Runner', null=b'True', permissions={b'read': True, b'execute': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='organization',
|
model_name='organization',
|
||||||
name='admin_role',
|
name='admin_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage all aspects of this organization', parent_role=b'singleton:System Administrator', to='main.Role', role_name=b'Organization Administrator', null=b'True', permissions={b'write': True, b'use': True, b'scm_update': True, b'execute': True, b'read': True, b'create': True, b'update': True, b'delete': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='organization',
|
model_name='organization',
|
||||||
name='auditor_role',
|
name='auditor_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this organization', parent_role=b'singleton:System Auditor', to='main.Role', role_name=b'Organization Auditor', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='organization',
|
model_name='organization',
|
||||||
name='member_role',
|
name='member_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this organization', parent_role=b'admin_role', to='main.Role', role_name=b'Organization Member', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='project',
|
model_name='project',
|
||||||
name='admin_role',
|
name='admin_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this project', parent_role=[b'organization.admin_role', b'singleton:System Administrator'], to='main.Role', role_name=b'Project Administrator', null=b'True', permissions={b'all': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='project',
|
model_name='project',
|
||||||
name='auditor_role',
|
name='auditor_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this project', parent_role=[b'organization.auditor_role', b'singleton:System Auditor'], to='main.Role', role_name=b'Project Auditor', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='project',
|
model_name='project',
|
||||||
name='member_role',
|
name='member_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Implies membership within this project', parent_role=None, to='main.Role', role_name=b'Project Member', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='project',
|
model_name='project',
|
||||||
name='scm_update_role',
|
name='scm_update_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update this project from the source control management system', parent_role=b'admin_role', to='main.Role', role_name=b'Project Updater', null=b'True', permissions={b'scm_update': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='team',
|
model_name='team',
|
||||||
name='admin_role',
|
name='admin_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this team', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Team Administrator', null=b'True', permissions={b'write': True, b'use': True, b'scm_update': True, b'execute': True, b'read': True, b'create': True, b'update': True, b'delete': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='team',
|
model_name='team',
|
||||||
name='auditor_role',
|
name='auditor_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this team', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Team Auditor', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='team',
|
model_name='team',
|
||||||
name='member_role',
|
name='member_role',
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this team', parent_role=b'admin_role', to='main.Role', role_name=b'Team Member', null=b'True', permissions={b'read': True}),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
migrations.AlterIndexTogether(
|
migrations.AlterIndexTogether(
|
||||||
name='rolepermission',
|
name='rolepermission',
|
||||||
index_together=set([('content_type', 'object_id')]),
|
index_together=set([('content_type', 'object_id')]),
|
||||||
@@ -240,11 +253,6 @@ class Migration(migrations.Migration):
|
|||||||
name='deprecated_projects',
|
name='deprecated_projects',
|
||||||
field=models.ManyToManyField(related_name='deprecated_organizations', to='main.Project', blank=True),
|
field=models.ManyToManyField(related_name='deprecated_organizations', to='main.Project', blank=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='organization',
|
|
||||||
field=models.ForeignKey(related_name='projects', to='main.Organization', blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
migrations.RenameField(
|
||||||
'Credential',
|
'Credential',
|
||||||
'team',
|
'team',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.RunPython(rbac.init_rbac_migration),
|
||||||
migrations.RunPython(rbac.migrate_users),
|
migrations.RunPython(rbac.migrate_users),
|
||||||
migrations.RunPython(rbac.migrate_organization),
|
migrations.RunPython(rbac.migrate_organization),
|
||||||
migrations.RunPython(rbac.migrate_team),
|
migrations.RunPython(rbac.migrate_team),
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import logging
|
|||||||
|
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from awx.main.utils import getattrd
|
from awx.main.utils import getattrd, set_current_apps
|
||||||
import _old_access as old_access
|
|
||||||
|
|
||||||
|
import _old_access as old_access
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def log_migration(wrapped):
|
def log_migration(wrapped):
|
||||||
@@ -25,39 +26,62 @@ def log_migration(wrapped):
|
|||||||
return wrapped(*args, **kwargs)
|
return wrapped(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
@log_migration
|
||||||
|
def init_rbac_migration(apps, schema_editor):
|
||||||
|
set_current_apps(apps)
|
||||||
|
|
||||||
@log_migration
|
@log_migration
|
||||||
def migrate_users(apps, schema_editor):
|
def migrate_users(apps, schema_editor):
|
||||||
User = apps.get_model('auth', "User")
|
User = apps.get_model('auth', "User")
|
||||||
Role = apps.get_model('main', "Role")
|
Role = apps.get_model('main', "Role")
|
||||||
RolePermission = apps.get_model('main', "RolePermission")
|
RolePermission = apps.get_model('main', "RolePermission")
|
||||||
ContentType = apps.get_model('contenttypes', "ContentType")
|
ContentType = apps.get_model('contenttypes', "ContentType")
|
||||||
|
user_content_type = ContentType.objects.get_for_model(User)
|
||||||
|
|
||||||
for user in User.objects.iterator():
|
for user in User.objects.iterator():
|
||||||
|
user.save()
|
||||||
try:
|
try:
|
||||||
Role.objects.get(content_type=ContentType.objects.get_for_model(User), object_id=user.id)
|
Role.objects.get(content_type=user_content_type, object_id=user.id)
|
||||||
logger.info(smart_text(u"found existing role for user: {}".format(user.username)))
|
logger.info(smart_text(u"found existing role for user: {}".format(user.username)))
|
||||||
except Role.DoesNotExist:
|
except Role.DoesNotExist:
|
||||||
role = Role.objects.create(
|
role = Role.objects.create(
|
||||||
|
created=now(),
|
||||||
|
modified=now(),
|
||||||
singleton_name = smart_text(u'{}-admin_role'.format(user.username)),
|
singleton_name = smart_text(u'{}-admin_role'.format(user.username)),
|
||||||
content_object = user,
|
content_type = user_content_type,
|
||||||
|
object_id = user.id
|
||||||
)
|
)
|
||||||
role.members.add(user)
|
role.members.add(user)
|
||||||
RolePermission.objects.create(
|
RolePermission.objects.create(
|
||||||
|
created=now(),
|
||||||
|
modified=now(),
|
||||||
role = role,
|
role = role,
|
||||||
resource = user,
|
content_type = user_content_type,
|
||||||
|
object_id = user.id,
|
||||||
create=1, read=1, write=1, delete=1, update=1,
|
create=1, read=1, write=1, delete=1, update=1,
|
||||||
execute=1, scm_update=1, use=1,
|
execute=1, scm_update=1, use=1,
|
||||||
)
|
)
|
||||||
logger.info(smart_text(u"migrating to new role for user: {}".format(user.username)))
|
logger.info(smart_text(u"migrating to new role for user: {}".format(user.username)))
|
||||||
|
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
Role.singleton('System Administrator').members.add(user)
|
if Role.objects.filter(singleton_name='System Administrator').exists():
|
||||||
|
sa_role = Role.objects.get(singleton_name='System Administrator')
|
||||||
|
else:
|
||||||
|
sa_role = Role.objects.create(
|
||||||
|
created=now(),
|
||||||
|
modified=now(),
|
||||||
|
singleton_name='System Administrator',
|
||||||
|
name='System Administrator'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa_role.members.add(user)
|
||||||
logger.warning(smart_text(u"added superuser: {}".format(user.username)))
|
logger.warning(smart_text(u"added superuser: {}".format(user.username)))
|
||||||
|
|
||||||
@log_migration
|
@log_migration
|
||||||
def migrate_organization(apps, schema_editor):
|
def migrate_organization(apps, schema_editor):
|
||||||
Organization = apps.get_model('main', "Organization")
|
Organization = apps.get_model('main', "Organization")
|
||||||
for org in Organization.objects.iterator():
|
for org in Organization.objects.iterator():
|
||||||
|
org.save() # force creates missing roles
|
||||||
for admin in org.deprecated_admins.all():
|
for admin in org.deprecated_admins.all():
|
||||||
org.admin_role.members.add(admin)
|
org.admin_role.members.add(admin)
|
||||||
logger.info(smart_text(u"added admin: {}, {}".format(org.name, admin.username)))
|
logger.info(smart_text(u"added admin: {}, {}".format(org.name, admin.username)))
|
||||||
@@ -69,6 +93,7 @@ def migrate_organization(apps, schema_editor):
|
|||||||
def migrate_team(apps, schema_editor):
|
def migrate_team(apps, schema_editor):
|
||||||
Team = apps.get_model('main', 'Team')
|
Team = apps.get_model('main', 'Team')
|
||||||
for t in Team.objects.iterator():
|
for t in Team.objects.iterator():
|
||||||
|
t.save()
|
||||||
for user in t.deprecated_users.all():
|
for user in t.deprecated_users.all():
|
||||||
t.member_role.members.add(user)
|
t.member_role.members.add(user)
|
||||||
logger.info(smart_text(u"team: {}, added user: {}".format(t.name, user.username)))
|
logger.info(smart_text(u"team: {}, added user: {}".format(t.name, user.username)))
|
||||||
@@ -110,7 +135,7 @@ def _discover_credentials(instances, cred, orgfunc):
|
|||||||
orgs[orgfunc(inst)].append(inst)
|
orgs[orgfunc(inst)].append(inst)
|
||||||
|
|
||||||
if len(orgs) == 1:
|
if len(orgs) == 1:
|
||||||
_update_credential_parents(instances[0].inventory.organization, cred)
|
_update_credential_parents(orgfunc(instances[0]), cred)
|
||||||
else:
|
else:
|
||||||
for pos, org in enumerate(orgs):
|
for pos, org in enumerate(orgs):
|
||||||
if pos == 0:
|
if pos == 0:
|
||||||
@@ -135,9 +160,14 @@ def migrate_credential(apps, schema_editor):
|
|||||||
Credential = apps.get_model('main', "Credential")
|
Credential = apps.get_model('main', "Credential")
|
||||||
JobTemplate = apps.get_model('main', 'JobTemplate')
|
JobTemplate = apps.get_model('main', 'JobTemplate')
|
||||||
Project = apps.get_model('main', 'Project')
|
Project = apps.get_model('main', 'Project')
|
||||||
|
Role = apps.get_model('main', 'Role')
|
||||||
|
User = apps.get_model('auth', 'User')
|
||||||
InventorySource = apps.get_model('main', 'InventorySource')
|
InventorySource = apps.get_model('main', 'InventorySource')
|
||||||
|
ContentType = apps.get_model('contenttypes', "ContentType")
|
||||||
|
user_content_type = ContentType.objects.get_for_model(User)
|
||||||
|
|
||||||
for cred in Credential.objects.iterator():
|
for cred in Credential.objects.iterator():
|
||||||
|
cred.save()
|
||||||
results = (JobTemplate.objects.filter(Q(credential=cred) | Q(cloud_credential=cred)).all() or
|
results = (JobTemplate.objects.filter(Q(credential=cred) | Q(cloud_credential=cred)).all() or
|
||||||
InventorySource.objects.filter(credential=cred).all())
|
InventorySource.objects.filter(credential=cred).all())
|
||||||
if results:
|
if results:
|
||||||
@@ -164,7 +194,8 @@ def migrate_credential(apps, schema_editor):
|
|||||||
cred.save()
|
cred.save()
|
||||||
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host)))
|
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host)))
|
||||||
elif cred.deprecated_user is not None:
|
elif cred.deprecated_user is not None:
|
||||||
cred.deprecated_user.admin_role.children.add(cred.owner_role)
|
user_admin_role = Role.objects.get(content_type=user_content_type, object_id=cred.deprecated_user.id)
|
||||||
|
user_admin_role.children.add(cred.owner_role)
|
||||||
cred.deprecated_user, cred.deprecated_team = None, None
|
cred.deprecated_user, cred.deprecated_team = None, None
|
||||||
cred.save()
|
cred.save()
|
||||||
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host, )))
|
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host, )))
|
||||||
@@ -191,6 +222,7 @@ def migrate_inventory(apps, schema_editor):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
for inventory in Inventory.objects.iterator():
|
for inventory in Inventory.objects.iterator():
|
||||||
|
inventory.save()
|
||||||
for perm in Permission.objects.filter(inventory=inventory):
|
for perm in Permission.objects.filter(inventory=inventory):
|
||||||
role = None
|
role = None
|
||||||
execrole = None
|
execrole = None
|
||||||
@@ -238,16 +270,18 @@ def migrate_projects(apps, schema_editor):
|
|||||||
|
|
||||||
# Migrate projects to single organizations, duplicating as necessary
|
# Migrate projects to single organizations, duplicating as necessary
|
||||||
for project in Project.objects.iterator():
|
for project in Project.objects.iterator():
|
||||||
|
project.save()
|
||||||
original_project_name = project.name
|
original_project_name = project.name
|
||||||
project_orgs = project.deprecated_organizations.distinct().all()
|
project_orgs = project.deprecated_organizations.distinct().all()
|
||||||
|
|
||||||
if len(project_orgs) > 1:
|
if len(project_orgs) >= 1:
|
||||||
first_org = None
|
first_org = None
|
||||||
for org in project_orgs:
|
for org in project_orgs:
|
||||||
if first_org is None:
|
if first_org is None:
|
||||||
# For the first org, re-use our existing Project object, so don't do the below duplication effort
|
# For the first org, re-use our existing Project object, so don't do the below duplication effort
|
||||||
first_org = org
|
first_org = org
|
||||||
project.name = smart_text(u'{} - {}'.format(first_org.name, original_project_name))
|
if len(project_orgs) > 1:
|
||||||
|
project.name = smart_text(u'{} - {}'.format(first_org.name, original_project_name))
|
||||||
project.organization = first_org
|
project.organization = first_org
|
||||||
project.save()
|
project.save()
|
||||||
else:
|
else:
|
||||||
@@ -349,6 +383,7 @@ def migrate_job_templates(apps, schema_editor):
|
|||||||
Permission = apps.get_model('main', 'Permission')
|
Permission = apps.get_model('main', 'Permission')
|
||||||
|
|
||||||
for jt in JobTemplate.objects.iterator():
|
for jt in JobTemplate.objects.iterator():
|
||||||
|
jt.save()
|
||||||
permission = Permission.objects.filter(
|
permission = Permission.objects.filter(
|
||||||
inventory=jt.inventory,
|
inventory=jt.inventory,
|
||||||
project=jt.project,
|
project=jt.project,
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class Role(CommonModelNameNotUnique):
|
|||||||
|
|
||||||
singleton_name = models.TextField(null=True, default=None, db_index=True, unique=True)
|
singleton_name = models.TextField(null=True, default=None, db_index=True, unique=True)
|
||||||
parents = models.ManyToManyField('Role', related_name='children')
|
parents = models.ManyToManyField('Role', related_name='children')
|
||||||
|
implicit_parents = models.TextField(null=False, default='[]')
|
||||||
ancestors = models.ManyToManyField('Role', related_name='descendents') # auto-generated by `rebuild_role_ancestor_list`
|
ancestors = models.ManyToManyField('Role', related_name='descendents') # auto-generated by `rebuild_role_ancestor_list`
|
||||||
members = models.ManyToManyField('auth.User', related_name='roles')
|
members = models.ManyToManyField('auth.User', related_name='roles')
|
||||||
content_type = models.ForeignKey(ContentType, null=True, default=None)
|
content_type = models.ForeignKey(ContentType, null=True, default=None)
|
||||||
|
|||||||
@@ -91,6 +91,46 @@ def test_project_migration():
|
|||||||
assert o2.projects.all()[0].jobtemplates.count() == 1
|
assert o2.projects.all()[0].jobtemplates.count() == 1
|
||||||
assert o3.projects.all()[0].jobtemplates.count() == 0
|
assert o3.projects.all()[0].jobtemplates.count() == 0
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_single_org_project_migration(organization):
|
||||||
|
project = Project.objects.create(name='my project',
|
||||||
|
description="description",
|
||||||
|
organization=None)
|
||||||
|
organization.deprecated_projects.add(project)
|
||||||
|
assert project.organization is None
|
||||||
|
rbac.migrate_projects(apps, None)
|
||||||
|
project = Project.objects.get(id=project.id)
|
||||||
|
assert project.organization.id == organization.id
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_no_org_project_migration(organization):
|
||||||
|
project = Project.objects.create(name='my project',
|
||||||
|
description="description",
|
||||||
|
organization=None)
|
||||||
|
assert project.organization is None
|
||||||
|
rbac.migrate_projects(apps, None)
|
||||||
|
assert project.organization is None
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_multi_org_project_migration():
|
||||||
|
org1 = Organization.objects.create(name="org1", description="org1 desc")
|
||||||
|
org2 = Organization.objects.create(name="org2", description="org2 desc")
|
||||||
|
project = Project.objects.create(name='my project',
|
||||||
|
description="description",
|
||||||
|
organization=None)
|
||||||
|
|
||||||
|
assert Project.objects.all().count() == 1
|
||||||
|
assert Project.objects.filter(organization=org1).count() == 0
|
||||||
|
assert Project.objects.filter(organization=org2).count() == 0
|
||||||
|
|
||||||
|
project.deprecated_organizations.add(org1)
|
||||||
|
project.deprecated_organizations.add(org2)
|
||||||
|
assert project.organization is None
|
||||||
|
rbac.migrate_projects(apps, None)
|
||||||
|
assert Project.objects.filter(organization=org1).count() == 1
|
||||||
|
assert Project.objects.filter(organization=org2).count() == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_project_user_project(user_project, project, user):
|
def test_project_user_project(user_project, project, user):
|
||||||
u = user('owner')
|
u = user('owner')
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import tempfile
|
|||||||
from rest_framework.exceptions import ParseError, PermissionDenied
|
from rest_framework.exceptions import ParseError, PermissionDenied
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.apps import apps
|
||||||
|
|
||||||
# PyCrypto
|
# PyCrypto
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
@@ -30,7 +31,8 @@ __all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore',
|
|||||||
'get_ansible_version', 'get_ssh_version', 'get_awx_version', 'update_scm_url',
|
'get_ansible_version', 'get_ssh_version', 'get_awx_version', 'update_scm_url',
|
||||||
'get_type_for_model', 'get_model_for_type', 'to_python_boolean',
|
'get_type_for_model', 'get_model_for_type', 'to_python_boolean',
|
||||||
'ignore_inventory_computed_fields', 'ignore_inventory_group_removal',
|
'ignore_inventory_computed_fields', 'ignore_inventory_group_removal',
|
||||||
'_inventory_updates', 'get_pk_from_dict', 'getattrd', 'NoDefaultProvided']
|
'_inventory_updates', 'get_pk_from_dict', 'getattrd', 'NoDefaultProvided',
|
||||||
|
'get_current_apps', 'set_current_apps']
|
||||||
|
|
||||||
|
|
||||||
def get_object_or_400(klass, *args, **kwargs):
|
def get_object_or_400(klass, *args, **kwargs):
|
||||||
@@ -556,3 +558,11 @@ def getattrd(obj, name, default=NoDefaultProvided):
|
|||||||
return default
|
return default
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
current_apps = apps
|
||||||
|
def set_current_apps(apps):
|
||||||
|
global current_apps
|
||||||
|
current_apps = apps
|
||||||
|
|
||||||
|
def get_current_apps():
|
||||||
|
global current_apps
|
||||||
|
return current_apps
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ DJANGO_SETTINGS_MODULE = awx.settings.development
|
|||||||
python_paths = awx/lib/site-packages
|
python_paths = awx/lib/site-packages
|
||||||
site_dirs = awx/lib/site-packages
|
site_dirs = awx/lib/site-packages
|
||||||
python_files = *.py
|
python_files = *.py
|
||||||
addopts = --reuse-db
|
addopts = --reuse-db --nomigrations
|
||||||
markers =
|
markers =
|
||||||
ac: access control test
|
ac: access control test
|
||||||
license_feature: ensure license features are accessible or not depending on license
|
license_feature: ensure license features are accessible or not depending on license
|
||||||
|
|||||||
Reference in New Issue
Block a user