fixed merge conflict

This commit is contained in:
John Mitchell
2016-07-22 15:01:58 -04:00
20 changed files with 175 additions and 10 deletions

View File

@@ -918,6 +918,19 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
args=(obj.last_update.pk,))
return res
def validate(self, attrs):
organization = None
if 'organization' in attrs:
organization = attrs['organization']
elif self.instance:
organization = self.instance.organization
view = self.context.get('view', None)
if not organization and not view.request.user.is_superuser:
# Only allow super users to create orgless projects
raise serializers.ValidationError('Organization is missing')
return super(ProjectSerializer, self).validate(attrs)
class ProjectPlaybooksSerializer(ProjectSerializer):

View File

@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='credential',
name='read_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_auditor', b'use_role', b'admin_role', b'organization.auditor_role'], to='main.Role', null=b'True'),
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_auditor', b'organization.auditor_role', b'use_role', b'admin_role'], to='main.Role', null=b'True'),
),
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
migrations.RunPython(rbac.rebuild_role_hierarchy),

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from awx.main.migrations import _rbac as rbac
from awx.main.migrations import _team_cleanup as team_cleanup
from awx.main.migrations import _migration_utils as migration_utils
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0026_v300_credential_unique'),
]
operations = [
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
migrations.RunPython(team_cleanup.migrate_team),
migrations.RunPython(rbac.rebuild_role_hierarchy),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import awx.main.fields
class Migration(migrations.Migration):
dependencies = [
('main', '0027_v300_team_migrations'),
]
operations = [
migrations.AlterField(
model_name='team',
name='organization',
field=models.ForeignKey(related_name='teams', to='main.Organization'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,30 @@
# Python
import logging
from django.utils.encoding import smart_text
logger = logging.getLogger(__name__)
def log_migration(wrapped):
'''setup the logging mechanism for each migration method
as it runs, Django resets this, so we use a decorator
to re-add the handler for each method.
'''
handler = logging.FileHandler("/tmp/tower_rbac_migrations.log", mode="a", encoding="UTF-8")
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setLevel(logging.DEBUG)
handler.setFormatter(formatter)
def wrapper(*args, **kwargs):
logger.handlers = []
logger.addHandler(handler)
return wrapped(*args, **kwargs)
return wrapper
@log_migration
def migrate_team(apps, schema_editor):
'''If an orphan team exists that is still active, delete it.'''
Team = apps.get_model('main', 'Team')
for team in Team.objects.iterator():
if team.organization is None:
logger.info(smart_text(u"Deleting orphaned team: {}".format(team.name)))
team.delete()

View File

@@ -92,8 +92,8 @@ class Team(CommonModelNameNotUnique, ResourceMixin):
organization = models.ForeignKey(
'Organization',
blank=False,
null=True,
on_delete=models.SET_NULL,
null=False,
on_delete=models.CASCADE,
related_name='teams',
)
deprecated_projects = models.ManyToManyField(

View File

@@ -114,3 +114,19 @@ def test_create_project(post, organization, org_admin, org_member, admin, rando,
assert result.status_code == expected_status_code
if expected_status_code == 201:
assert Project.objects.filter(name='Project', organization=organization).exists()
@pytest.mark.django_db()
def test_create_project_null_organization(post, organization, admin):
post(reverse('api:project_list'), { 'name': 't', 'organization': None}, admin, expect=201)
@pytest.mark.django_db()
def test_create_project_null_organization_xfail(post, organization, org_admin):
post(reverse('api:project_list'), { 'name': 't', 'organization': None}, org_admin, expect=400)
@pytest.mark.django_db()
def test_patch_project_null_organization(patch, organization, project, admin):
patch(reverse('api:project_detail', args=(project.id,)), { 'name': 't', 'organization': organization.id}, admin, expect=200)
@pytest.mark.django_db()
def test_patch_project_null_organization_xfail(patch, project, org_admin):
patch(reverse('api:project_detail', args=(project.id,)), { 'name': 't', 'organization': None}, org_admin, expect=400)

View File

@@ -25,6 +25,7 @@ DATABASES = {
'NAME': 'awx-dev',
'USER': 'awx-dev',
'PASSWORD': 'AWXsome1',
'ATOMIC_REQUESTS': True,
'HOST': 'postgres',
'PORT': '',
}

View File

@@ -68,6 +68,7 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
list: AllJobsList,
id: 'active-jobs',
url: GetBasePath('unified_jobs') + '?status__in=pending,waiting,running,completed,failed,successful,error,canceled,new&order_by=-finished',
pageSize: 20,
searchParams: search_params,
spinner: false
});
@@ -79,6 +80,7 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
parent_scope: $scope,
scope: scheduled_scope,
list: scheduledJobsList,
pageSize: 20,
id: 'scheduled-jobs-tab',
searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12',
url: scheduledJobsList.basePath

View File

@@ -389,6 +389,7 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
master = {};
// remove "type" field from search options
CredentialList = _.cloneDeep(CredentialList);
CredentialList.fields.kind.noSearch = true;
generator.inject(form, { mode: 'add', related: false, scope: $scope });
@@ -568,6 +569,7 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
relatedSets = {};
// remove "type" field from search options
CredentialList = _.cloneDeep(CredentialList);
CredentialList.fields.kind.noSearch = true;

View File

@@ -687,7 +687,7 @@ export default
scope.plays = [];
url = scope.job.url + 'job_plays/?page_size=' + scope.playsMaxRows + '&order=id';
url += (scope.search_play_name) ? '&play__icontains=' + scope.search_play_name : '';
url += (scope.search_play_name) ? '&play__icontains=' + encodeURIComponent(scope.search_play_name) : '';
url += (scope.search_play_status === 'failed') ? '&failed=true' : '';
scope.playsLoading = true;
Rest.setUrl(url);
@@ -786,7 +786,7 @@ export default
scope.tasks = [];
if (scope.selectedPlay) {
url = scope.job.url + 'job_tasks/?event_id=' + scope.selectedPlay;
url += (scope.search_task_name) ? '&task__icontains=' + scope.search_task_name : '';
url += (scope.search_task_name) ? '&task__icontains=' + encodeURIComponent(scope.search_task_name) : '';
url += (scope.search_task_status === 'failed') ? '&failed=true' : '';
url += '&page_size=' + scope.tasksMaxRows + '&order=id';
scope.plays.every(function(p, idx) {

View File

@@ -36,6 +36,8 @@ angular.module('JobTemplatesHelper', ['Utilities'])
// checkSCMStatus, getPlaybooks, callback,
// choicesCount = 0;
CredentialList = _.cloneDeep(CredentialList);
// The form uses awPopOverWatch directive to 'watch' scope.callback_help for changes. Each time the
// popover is activated, a function checks the value of scope.callback_help before constructing the content.
scope.setCallbackHelp = function() {

View File

@@ -13,6 +13,7 @@
form = GroupForm();
// remove "type" field from search options
CredentialList = _.cloneDeep(CredentialList);
CredentialList.fields.kind.noSearch = true;
$scope.formCancel = function(){

View File

@@ -15,6 +15,7 @@
form = GroupForm();
// remove "type" field from search options
CredentialList = _.cloneDeep(CredentialList);
CredentialList.fields.kind.noSearch = true;
$scope.formCancel = function(){

View File

@@ -25,8 +25,8 @@
host_name: $scope.hostName,
};
if ($scope.searchStr && $scope.searchStr !== ''){
params.or__play__icontains = $scope.searchStr;
params.or__task__icontains = $scope.searchStr;
params.or__play__icontains = encodeURIComponent($scope.searchStr);
params.or__task__icontains = encodeURIComponent($scope.searchStr);
}
switch($scope.activeFilter){

View File

@@ -47,6 +47,7 @@ export default
choicesCount = 0;
// remove "type" field from search options
CredentialList = _.cloneDeep(CredentialList);
CredentialList.fields.kind.noSearch = true;
CallbackHelpInit({ scope: $scope });
@@ -471,7 +472,7 @@ export default
});
}
else {
// job template doesn't exist
// job template doesn't exist
$scope.$emit("choicesReady");
}

View File

@@ -1,4 +1,20 @@
export default ['$compile', '$state', '$stateParams', 'EditSchedule', 'Wait', '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', function($compile, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope, CreateSelect2, ParseTypeChange) {
export default ['$filter', '$compile', '$state', '$stateParams', 'EditSchedule', 'Wait', '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange',
function($filter, $compile, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope, CreateSelect2, ParseTypeChange) {
$scope.processSchedulerEndDt = function(){
// set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight
var dt = new Date($scope.schedulerUTCTime);
// increment date by 1 day
dt.setDate(dt.getDate() + 1);
var month = $filter('schZeroPad')(dt.getMonth() + 1, 2),
day = $filter('schZeroPad')(dt.getDate(), 2);
$scope.$parent.schedulerEndDt = month + '/' + day + '/' + dt.getFullYear();
};
// initial end @ midnight values
$scope.schedulerEndHour = "00";
$scope.schedulerEndMinute = "00";
$scope.schedulerEndSecond = "00";
$scope.$on("ScheduleFormCreated", function(e, scope) {
$scope.hideForm = false;
$scope = angular.extend($scope, scope);

View File

@@ -85,7 +85,7 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', '$log', fu
if (needsRequest.length) {
// make the options request to reutrn the typeOptions
var url = needsRequest[0].basePath ? GetBasePath(needsRequest[0].basePath) : basePath;
if(url.indexOf('null') === 0 ){
if(url.indexOf('null') === -1 ){
Rest.setUrl(url);
Rest.options()
.success(function (data) {