mirror of
https://github.com/ansible/awx.git
synced 2026-01-21 14:38:00 -03:30
fixed merge conflict
This commit is contained in:
commit
03204f8dc0
@ -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):
|
||||
|
||||
|
||||
@ -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),
|
||||
|
||||
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',
|
||||
blank=False,
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
null=False,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='teams',
|
||||
)
|
||||
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
|
||||
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)
|
||||
|
||||
@ -25,6 +25,7 @@ DATABASES = {
|
||||
'NAME': 'awx-dev',
|
||||
'USER': 'awx-dev',
|
||||
'PASSWORD': 'AWXsome1',
|
||||
'ATOMIC_REQUESTS': True,
|
||||
'HOST': 'postgres',
|
||||
'PORT': '',
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
form = GroupForm();
|
||||
|
||||
// remove "type" field from search options
|
||||
CredentialList = _.cloneDeep(CredentialList);
|
||||
CredentialList.fields.kind.noSearch = true;
|
||||
|
||||
$scope.formCancel = function(){
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
form = GroupForm();
|
||||
|
||||
// remove "type" field from search options
|
||||
CredentialList = _.cloneDeep(CredentialList);
|
||||
CredentialList.fields.kind.noSearch = true;
|
||||
|
||||
$scope.formCancel = function(){
|
||||
|
||||
@ -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){
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
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
|
||||
Loading…
x
Reference in New Issue
Block a user