fixed merge conflict

This commit is contained in:
John Mitchell 2016-07-22 15:01:58 -04:00
commit 03204f8dc0
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) {

26
docs/licenses/cowsay.txt Normal file
View File

@ -0,0 +1,26 @@
cowsay is licensed under the following MIT license:
====
Copyright (c) 2012 Fabio Crisci <fabio.crisci@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
====
The original idea of cowsay come from [Tony Monroe](http://www.nog.net/~tony/) - [cowsay](https://github.com/schacon/cowsay)

View File

@ -0,0 +1,13 @@
Font License
Applies to all desktop and webfont files in the following directory: font-awesome/fonts/.
License: SIL OFL 1.1
URL: http://scripts.sil.org/OFL
Code License
Applies to all CSS and LESS files in the following directories: font-awesome/css/, font-awesome/less/, and font-awesome/scss/.
License: MIT License
URL: http://opensource.org/licenses/mit-license.html