mirror of
https://github.com/ansible/awx.git
synced 2026-05-16 13:57:39 -02:30
fixed merge conflict
This commit is contained in:
@@ -918,6 +918,19 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
|
|||||||
args=(obj.last_update.pk,))
|
args=(obj.last_update.pk,))
|
||||||
return res
|
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):
|
class ProjectPlaybooksSerializer(ProjectSerializer):
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='credential',
|
model_name='credential',
|
||||||
name='read_role',
|
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(migration_utils.set_current_apps_for_migrations),
|
||||||
migrations.RunPython(rbac.rebuild_role_hierarchy),
|
migrations.RunPython(rbac.rebuild_role_hierarchy),
|
||||||
|
|||||||
20
awx/main/migrations/0027_v300_team_migrations.py
Normal file
20
awx/main/migrations/0027_v300_team_migrations.py
Normal 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),
|
||||||
|
]
|
||||||
21
awx/main/migrations/0028_v300_org_team_cascade.py
Normal file
21
awx/main/migrations/0028_v300_org_team_cascade.py
Normal 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,
|
||||||
|
),
|
||||||
|
]
|
||||||
30
awx/main/migrations/_team_cleanup.py
Normal file
30
awx/main/migrations/_team_cleanup.py
Normal 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()
|
||||||
@@ -92,8 +92,8 @@ class Team(CommonModelNameNotUnique, ResourceMixin):
|
|||||||
organization = models.ForeignKey(
|
organization = models.ForeignKey(
|
||||||
'Organization',
|
'Organization',
|
||||||
blank=False,
|
blank=False,
|
||||||
null=True,
|
null=False,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.CASCADE,
|
||||||
related_name='teams',
|
related_name='teams',
|
||||||
)
|
)
|
||||||
deprecated_projects = models.ManyToManyField(
|
deprecated_projects = models.ManyToManyField(
|
||||||
|
|||||||
@@ -114,3 +114,19 @@ def test_create_project(post, organization, org_admin, org_member, admin, rando,
|
|||||||
assert result.status_code == expected_status_code
|
assert result.status_code == expected_status_code
|
||||||
if expected_status_code == 201:
|
if expected_status_code == 201:
|
||||||
assert Project.objects.filter(name='Project', organization=organization).exists()
|
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)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ DATABASES = {
|
|||||||
'NAME': 'awx-dev',
|
'NAME': 'awx-dev',
|
||||||
'USER': 'awx-dev',
|
'USER': 'awx-dev',
|
||||||
'PASSWORD': 'AWXsome1',
|
'PASSWORD': 'AWXsome1',
|
||||||
|
'ATOMIC_REQUESTS': True,
|
||||||
'HOST': 'postgres',
|
'HOST': 'postgres',
|
||||||
'PORT': '',
|
'PORT': '',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
|
|||||||
list: AllJobsList,
|
list: AllJobsList,
|
||||||
id: 'active-jobs',
|
id: 'active-jobs',
|
||||||
url: GetBasePath('unified_jobs') + '?status__in=pending,waiting,running,completed,failed,successful,error,canceled,new&order_by=-finished',
|
url: GetBasePath('unified_jobs') + '?status__in=pending,waiting,running,completed,failed,successful,error,canceled,new&order_by=-finished',
|
||||||
|
pageSize: 20,
|
||||||
searchParams: search_params,
|
searchParams: search_params,
|
||||||
spinner: false
|
spinner: false
|
||||||
});
|
});
|
||||||
@@ -79,6 +80,7 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
|
|||||||
parent_scope: $scope,
|
parent_scope: $scope,
|
||||||
scope: scheduled_scope,
|
scope: scheduled_scope,
|
||||||
list: scheduledJobsList,
|
list: scheduledJobsList,
|
||||||
|
pageSize: 20,
|
||||||
id: 'scheduled-jobs-tab',
|
id: 'scheduled-jobs-tab',
|
||||||
searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12',
|
searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12',
|
||||||
url: scheduledJobsList.basePath
|
url: scheduledJobsList.basePath
|
||||||
|
|||||||
@@ -389,6 +389,7 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
|
|||||||
master = {};
|
master = {};
|
||||||
|
|
||||||
// remove "type" field from search options
|
// remove "type" field from search options
|
||||||
|
CredentialList = _.cloneDeep(CredentialList);
|
||||||
CredentialList.fields.kind.noSearch = true;
|
CredentialList.fields.kind.noSearch = true;
|
||||||
|
|
||||||
generator.inject(form, { mode: 'add', related: false, scope: $scope });
|
generator.inject(form, { mode: 'add', related: false, scope: $scope });
|
||||||
@@ -568,6 +569,7 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
|
|||||||
relatedSets = {};
|
relatedSets = {};
|
||||||
|
|
||||||
// remove "type" field from search options
|
// remove "type" field from search options
|
||||||
|
CredentialList = _.cloneDeep(CredentialList);
|
||||||
CredentialList.fields.kind.noSearch = true;
|
CredentialList.fields.kind.noSearch = true;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -687,7 +687,7 @@ export default
|
|||||||
scope.plays = [];
|
scope.plays = [];
|
||||||
|
|
||||||
url = scope.job.url + 'job_plays/?page_size=' + scope.playsMaxRows + '&order=id';
|
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' : '';
|
url += (scope.search_play_status === 'failed') ? '&failed=true' : '';
|
||||||
scope.playsLoading = true;
|
scope.playsLoading = true;
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
@@ -786,7 +786,7 @@ export default
|
|||||||
scope.tasks = [];
|
scope.tasks = [];
|
||||||
if (scope.selectedPlay) {
|
if (scope.selectedPlay) {
|
||||||
url = scope.job.url + 'job_tasks/?event_id=' + 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 += (scope.search_task_status === 'failed') ? '&failed=true' : '';
|
||||||
url += '&page_size=' + scope.tasksMaxRows + '&order=id';
|
url += '&page_size=' + scope.tasksMaxRows + '&order=id';
|
||||||
scope.plays.every(function(p, idx) {
|
scope.plays.every(function(p, idx) {
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ angular.module('JobTemplatesHelper', ['Utilities'])
|
|||||||
// checkSCMStatus, getPlaybooks, callback,
|
// checkSCMStatus, getPlaybooks, callback,
|
||||||
// choicesCount = 0;
|
// choicesCount = 0;
|
||||||
|
|
||||||
|
CredentialList = _.cloneDeep(CredentialList);
|
||||||
|
|
||||||
// The form uses awPopOverWatch directive to 'watch' scope.callback_help for changes. Each time the
|
// 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.
|
// popover is activated, a function checks the value of scope.callback_help before constructing the content.
|
||||||
scope.setCallbackHelp = function() {
|
scope.setCallbackHelp = function() {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
form = GroupForm();
|
form = GroupForm();
|
||||||
|
|
||||||
// remove "type" field from search options
|
// remove "type" field from search options
|
||||||
|
CredentialList = _.cloneDeep(CredentialList);
|
||||||
CredentialList.fields.kind.noSearch = true;
|
CredentialList.fields.kind.noSearch = true;
|
||||||
|
|
||||||
$scope.formCancel = function(){
|
$scope.formCancel = function(){
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
form = GroupForm();
|
form = GroupForm();
|
||||||
|
|
||||||
// remove "type" field from search options
|
// remove "type" field from search options
|
||||||
|
CredentialList = _.cloneDeep(CredentialList);
|
||||||
CredentialList.fields.kind.noSearch = true;
|
CredentialList.fields.kind.noSearch = true;
|
||||||
|
|
||||||
$scope.formCancel = function(){
|
$scope.formCancel = function(){
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
host_name: $scope.hostName,
|
host_name: $scope.hostName,
|
||||||
};
|
};
|
||||||
if ($scope.searchStr && $scope.searchStr !== ''){
|
if ($scope.searchStr && $scope.searchStr !== ''){
|
||||||
params.or__play__icontains = $scope.searchStr;
|
params.or__play__icontains = encodeURIComponent($scope.searchStr);
|
||||||
params.or__task__icontains = $scope.searchStr;
|
params.or__task__icontains = encodeURIComponent($scope.searchStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch($scope.activeFilter){
|
switch($scope.activeFilter){
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export default
|
|||||||
choicesCount = 0;
|
choicesCount = 0;
|
||||||
|
|
||||||
// remove "type" field from search options
|
// remove "type" field from search options
|
||||||
|
CredentialList = _.cloneDeep(CredentialList);
|
||||||
CredentialList.fields.kind.noSearch = true;
|
CredentialList.fields.kind.noSearch = true;
|
||||||
|
|
||||||
CallbackHelpInit({ scope: $scope });
|
CallbackHelpInit({ scope: $scope });
|
||||||
|
|||||||
@@ -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.$on("ScheduleFormCreated", function(e, scope) {
|
||||||
$scope.hideForm = false;
|
$scope.hideForm = false;
|
||||||
$scope = angular.extend($scope, scope);
|
$scope = angular.extend($scope, scope);
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', '$log', fu
|
|||||||
if (needsRequest.length) {
|
if (needsRequest.length) {
|
||||||
// make the options request to reutrn the typeOptions
|
// make the options request to reutrn the typeOptions
|
||||||
var url = needsRequest[0].basePath ? GetBasePath(needsRequest[0].basePath) : basePath;
|
var url = needsRequest[0].basePath ? GetBasePath(needsRequest[0].basePath) : basePath;
|
||||||
if(url.indexOf('null') === 0 ){
|
if(url.indexOf('null') === -1 ){
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
Rest.options()
|
Rest.options()
|
||||||
.success(function (data) {
|
.success(function (data) {
|
||||||
|
|||||||
26
docs/licenses/cowsay.txt
Normal file
26
docs/licenses/cowsay.txt
Normal 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)
|
||||||
13
docs/licenses/font-awesome.txt
Normal file
13
docs/licenses/font-awesome.txt
Normal 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
|
||||||
Reference in New Issue
Block a user