mirror of
https://github.com/ansible/awx.git
synced 2024-11-01 08:21:15 +03:00
Merge pull request #541 from matburt/database_config_source
Database config source
This commit is contained in:
commit
84c2e71907
@ -6,7 +6,6 @@ import urllib
|
||||
|
||||
# Django
|
||||
from django.utils.timezone import now as tz_now
|
||||
from django.conf import settings
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import authentication
|
||||
@ -15,7 +14,7 @@ from rest_framework import HTTP_HEADER_ENCODING
|
||||
|
||||
# AWX
|
||||
from awx.main.models import UnifiedJob, AuthToken
|
||||
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
class TokenAuthentication(authentication.TokenAuthentication):
|
||||
'''
|
||||
@ -90,7 +89,7 @@ class TokenAuthentication(authentication.TokenAuthentication):
|
||||
|
||||
# Token invalidated due to session limit config being reduced
|
||||
# Session limit reached invalidation will also take place on authentication
|
||||
if settings.AUTH_TOKEN_PER_USER != -1:
|
||||
if tower_settings.AUTH_TOKEN_PER_USER != -1:
|
||||
if not token.in_valid_tokens(now=now):
|
||||
token.invalidate(reason='limit_reached')
|
||||
raise exceptions.AuthenticationFailed(AuthToken.reason_long('limit_reached'))
|
||||
|
@ -144,6 +144,7 @@ class APIView(views.APIView):
|
||||
'new_in_220': getattr(self, 'new_in_220', False),
|
||||
'new_in_230': getattr(self, 'new_in_230', False),
|
||||
'new_in_240': getattr(self, 'new_in_240', False),
|
||||
'new_in_300': getattr(self, 'new_in_300', False),
|
||||
}
|
||||
|
||||
def get_description(self, html=False):
|
||||
@ -160,7 +161,7 @@ class APIView(views.APIView):
|
||||
'''
|
||||
ret = super(APIView, self).metadata(request)
|
||||
added_in_version = '1.2'
|
||||
for version in ('2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
|
||||
for version in ('3.0.0', '2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
|
||||
if getattr(self, 'new_in_%s' % version.replace('.', ''), False):
|
||||
added_in_version = version
|
||||
break
|
||||
|
@ -38,6 +38,7 @@ from awx.main.constants import SCHEDULEABLE_PROVIDERS
|
||||
from awx.main.models import * # noqa
|
||||
from awx.main.utils import get_type_for_model, get_model_for_type, build_url, timestamp_apiformat
|
||||
from awx.main.redact import REPLACE_STR
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
from awx.api.license import feature_enabled
|
||||
|
||||
@ -267,7 +268,7 @@ class BaseSerializer(serializers.ModelSerializer):
|
||||
return choices
|
||||
|
||||
def get_url(self, obj):
|
||||
if obj is None:
|
||||
if obj is None or not hasattr(obj, 'get_absolute_url'):
|
||||
return ''
|
||||
elif isinstance(obj, User):
|
||||
return reverse('api:user_detail', args=(obj.pk,))
|
||||
@ -521,8 +522,9 @@ class UnifiedJobSerializer(BaseSerializer):
|
||||
|
||||
def get_result_stdout(self, obj):
|
||||
obj_size = obj.result_stdout_size
|
||||
if obj_size > settings.STDOUT_MAX_BYTES_DISPLAY:
|
||||
return "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size, settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||
if obj_size > tower_settings.STDOUT_MAX_BYTES_DISPLAY:
|
||||
return "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size,
|
||||
tower_settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||
return obj.result_stdout
|
||||
|
||||
class UnifiedJobListSerializer(UnifiedJobSerializer):
|
||||
@ -569,8 +571,9 @@ class UnifiedJobStdoutSerializer(UnifiedJobSerializer):
|
||||
|
||||
def get_result_stdout(self, obj):
|
||||
obj_size = obj.result_stdout_size
|
||||
if obj_size > settings.STDOUT_MAX_BYTES_DISPLAY:
|
||||
return "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size, settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||
if obj_size > tower_settings.STDOUT_MAX_BYTES_DISPLAY:
|
||||
return "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size,
|
||||
tower_settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||
return obj.result_stdout
|
||||
|
||||
def get_types(self):
|
||||
@ -2106,6 +2109,39 @@ class ActivityStreamSerializer(BaseSerializer):
|
||||
last_name = obj.actor.last_name)
|
||||
return summary_fields
|
||||
|
||||
class TowerSettingsSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = TowerSettings
|
||||
fields = ('key', 'description', 'category', 'value', 'value_type', 'user')
|
||||
read_only_fields = ('description', 'category', 'value_type', 'user')
|
||||
|
||||
def from_native(self, data, files):
|
||||
if data['key'] not in settings.TOWER_SETTINGS_MANIFEST:
|
||||
self._errors = {'key': 'Key {0} is not a valid settings key'.format(data['key'])}
|
||||
return
|
||||
current_val = TowerSettings.objects.filter(key=data['key'])
|
||||
if current_val.exists():
|
||||
current_val.delete()
|
||||
manifest_val = settings.TOWER_SETTINGS_MANIFEST[data['key']]
|
||||
data['description'] = manifest_val['description']
|
||||
data['category'] = manifest_val['category']
|
||||
data['value_type'] = manifest_val['type']
|
||||
return super(TowerSettingsSerializer, self).from_native(data, files)
|
||||
|
||||
def validate(self, attrs):
|
||||
manifest = settings.TOWER_SETTINGS_MANIFEST
|
||||
if attrs['key'] not in manifest:
|
||||
raise serializers.ValidationError(dict(key=["Key {0} is not a valid settings key".format(attrs['key'])]))
|
||||
# TODO: Type checking/coercion, contextual validation
|
||||
return attrs
|
||||
|
||||
def save_object(self, obj, **kwargs):
|
||||
manifest_val = settings.TOWER_SETTINGS_MANIFEST[obj.key]
|
||||
obj.description = manifest_val['description']
|
||||
obj.category = manifest_val['category']
|
||||
obj.value_type = manifest_val['type']
|
||||
return super(TowerSettingsSerializer, self).save_object(obj, **kwargs)
|
||||
|
||||
class AuthTokenSerializer(serializers.Serializer):
|
||||
|
||||
|
@ -6,3 +6,4 @@
|
||||
{% if new_in_220 %}> _New in Ansible Tower 2.2.0_{% endif %}
|
||||
{% if new_in_230 %}> _New in Ansible Tower 2.3.0_{% endif %}
|
||||
{% if new_in_240 %}> _New in Ansible Tower 2.4.0_{% endif %}
|
||||
{% if new_in_300 %}> _New in Ansible Tower 3.0.0_{% endif %}
|
||||
|
@ -220,6 +220,10 @@ activity_stream_urls = patterns('awx.api.views',
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'activity_stream_detail'),
|
||||
)
|
||||
|
||||
settings_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'settings_list'),
|
||||
url(r'^reset/$', 'settings_reset'))
|
||||
|
||||
v1_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'api_v1_root_view'),
|
||||
url(r'^ping/$', 'api_v1_ping_view'),
|
||||
@ -230,6 +234,7 @@ v1_urls = patterns('awx.api.views',
|
||||
url(r'^dashboard/$', 'dashboard_view'),
|
||||
url(r'^dashboard/graphs/jobs/$', 'dashboard_jobs_graph_view'),
|
||||
url(r'^dashboard/graphs/inventory/$', 'dashboard_inventory_graph_view'),
|
||||
url(r'^settings/', include(settings_urls)),
|
||||
url(r'^schedules/', include(schedule_urls)),
|
||||
url(r'^organizations/', include(organization_urls)),
|
||||
url(r'^users/', include(user_urls)),
|
||||
|
@ -12,6 +12,7 @@ import socket
|
||||
import sys
|
||||
import errno
|
||||
from base64 import b64encode
|
||||
from collections import namedtuple
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
@ -69,6 +70,7 @@ from awx.api.renderers import * # noqa
|
||||
from awx.api.serializers import * # noqa
|
||||
from awx.fact.models import * # noqa
|
||||
from awx.main.utils import emit_websocket_notification
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
def api_exception_handler(exc):
|
||||
'''
|
||||
@ -113,6 +115,7 @@ class ApiV1RootView(APIView):
|
||||
data['authtoken'] = reverse('api:auth_token_view')
|
||||
data['ping'] = reverse('api:api_v1_ping_view')
|
||||
data['config'] = reverse('api:api_v1_config_view')
|
||||
data['settings'] = reverse('api:settings_list')
|
||||
data['me'] = reverse('api:user_me_list')
|
||||
data['dashboard'] = reverse('api:dashboard_view')
|
||||
data['organizations'] = reverse('api:organization_list')
|
||||
@ -189,9 +192,9 @@ class ApiV1ConfigView(APIView):
|
||||
'''Return various sitewide configuration settings.'''
|
||||
|
||||
license_reader = TaskSerializer()
|
||||
license_data = license_reader.from_file(show_key=request.user.is_superuser)
|
||||
license_data = license_reader.from_database(show_key=request.user.is_superuser)
|
||||
|
||||
pendo_state = settings.PENDO_TRACKING_STATE if settings.PENDO_TRACKING_STATE in ('off', 'anonymous', 'detailed') else 'off'
|
||||
pendo_state = tower_settings.PENDO_TRACKING_STATE if tower_settings.PENDO_TRACKING_STATE in ('off', 'anonymous', 'detailed') else 'off'
|
||||
|
||||
data = dict(
|
||||
time_zone=settings.TIME_ZONE,
|
||||
@ -261,9 +264,7 @@ class ApiV1ConfigView(APIView):
|
||||
|
||||
# If the license is valid, write it to disk.
|
||||
if license_data['valid_key']:
|
||||
fh = open(TASK_FILE, "w")
|
||||
fh.write(data_actual)
|
||||
fh.close()
|
||||
tower_settings.LICENSE = data_actual
|
||||
|
||||
# Spawn a task to ensure that MongoDB is started (or stopped)
|
||||
# as appropriate, based on whether the license uses it.
|
||||
@ -592,7 +593,7 @@ class AuthTokenView(APIView):
|
||||
# Note: This header is normally added in the middleware whenever an
|
||||
# auth token is included in the request header.
|
||||
headers = {
|
||||
'Auth-Token-Timeout': int(settings.AUTH_TOKEN_EXPIRATION)
|
||||
'Auth-Token-Timeout': int(tower_settings.AUTH_TOKEN_EXPIRATION)
|
||||
}
|
||||
return Response({'token': token.key, 'expires': token.expires}, headers=headers)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -2859,8 +2860,9 @@ class UnifiedJobStdout(RetrieveAPIView):
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
unified_job = self.get_object()
|
||||
obj_size = unified_job.result_stdout_size
|
||||
if request.accepted_renderer.format != 'txt_download' and obj_size > settings.STDOUT_MAX_BYTES_DISPLAY:
|
||||
response_message = "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size, settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||
if request.accepted_renderer.format != 'txt_download' and obj_size > tower_settings.STDOUT_MAX_BYTES_DISPLAY:
|
||||
response_message = "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size,
|
||||
tower_settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||
if request.accepted_renderer.format == 'json':
|
||||
return Response({'range': {'start': 0, 'end': 1, 'absolute_end': 1}, 'content': response_message})
|
||||
else:
|
||||
@ -2959,6 +2961,61 @@ class ActivityStreamDetail(RetrieveAPIView):
|
||||
# Okay, let it through.
|
||||
return super(type(self), self).get(request, *args, **kwargs)
|
||||
|
||||
class SettingsList(ListCreateAPIView):
|
||||
|
||||
model = TowerSettings
|
||||
serializer_class = TowerSettingsSerializer
|
||||
authentication_classes = [TokenGetAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
||||
new_in_300 = True
|
||||
filter_backends = ()
|
||||
|
||||
def get_queryset(self):
|
||||
# TODO: docs
|
||||
if not self.request.user.is_superuser:
|
||||
# NOTE: Shortcutting the rbac class due to the merging of the settings manifest and the database
|
||||
# we'll need to extend this more in the future when we have user settings
|
||||
return []
|
||||
SettingsTuple = namedtuple('Settings', ['key', 'description', 'category', 'value', 'value_type', 'user'])
|
||||
all_defined_settings = {s.key: SettingsTuple(s.key,
|
||||
s.description,
|
||||
s.category,
|
||||
s.value_converted,
|
||||
s.value_type,
|
||||
s.user) for s in TowerSettings.objects.all()}
|
||||
manifest_settings = settings.TOWER_SETTINGS_MANIFEST
|
||||
settings_actual = []
|
||||
for settings_key in manifest_settings:
|
||||
if settings_key in all_defined_settings:
|
||||
settings_actual.append(all_defined_settings[settings_key])
|
||||
else:
|
||||
m_entry = manifest_settings[settings_key]
|
||||
settings_actual.append(SettingsTuple(settings_key,
|
||||
m_entry['description'],
|
||||
m_entry['category'],
|
||||
m_entry['default'],
|
||||
m_entry['type'],
|
||||
None))
|
||||
return settings_actual
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
if not request.user.can_access(self.model, 'delete', None):
|
||||
raise PermissionDenied()
|
||||
TowerSettings.objects.all().delete()
|
||||
return Response()
|
||||
|
||||
class SettingsReset(APIView):
|
||||
|
||||
view_name = "Reset a settings value"
|
||||
new_in_300 = True
|
||||
|
||||
def post(self, request):
|
||||
# NOTE: Extend more with user settings
|
||||
if not request.user.can_access(TowerSettings, 'delete', None):
|
||||
raise PermissionDenied()
|
||||
settings_key = request.DATA.get('key', None)
|
||||
if settings_key is not None:
|
||||
TowerSettings.objects.filter(key=settings_key).delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
# Create view functions for all of the class-based views to simplify inclusion
|
||||
# in URL patterns and reverse URL lookups, converting CamelCase names to
|
||||
|
@ -7,7 +7,6 @@ import sys
|
||||
import logging
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.db.models import F, Q
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
@ -19,6 +18,7 @@ from awx.main.utils import * # noqa
|
||||
from awx.main.models import * # noqa
|
||||
from awx.api.license import LicenseForbids
|
||||
from awx.main.task_engine import TaskSerializer
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
__all__ = ['get_user_queryset', 'check_user_access']
|
||||
|
||||
@ -196,7 +196,7 @@ class UserAccess(BaseAccess):
|
||||
qs = self.model.objects.filter(is_active=True).distinct()
|
||||
if self.user.is_superuser:
|
||||
return qs
|
||||
if settings.ORG_ADMINS_CAN_SEE_ALL_USERS and self.user.admin_of_organizations.filter(active=True).exists():
|
||||
if tower_settings.ORG_ADMINS_CAN_SEE_ALL_USERS and self.user.admin_of_organizations.filter(active=True).exists():
|
||||
return qs
|
||||
return qs.filter(
|
||||
Q(pk=self.user.pk) |
|
||||
@ -1566,6 +1566,10 @@ class ActivityStreamAccess(BaseAccess):
|
||||
ad_hoc_command_qs = self.user.get_queryset(AdHocCommand)
|
||||
qs.filter(ad_hoc_command__in=ad_hoc_command_qs)
|
||||
|
||||
# TowerSettings Filter
|
||||
settings_qs = self.user.get_queryset(TowerSettings)
|
||||
qs.filter(tower_settings__in=settings_qs)
|
||||
|
||||
# organization_qs = self.user.get_queryset(Organization)
|
||||
# user_qs = self.user.get_queryset(User)
|
||||
# inventory_qs = self.user.get_queryset(Inventory)
|
||||
@ -1636,6 +1640,30 @@ class CustomInventoryScriptAccess(BaseAccess):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class TowerSettingsAccess(BaseAccess):
|
||||
'''
|
||||
- I can see settings when
|
||||
- I am a super user
|
||||
- I can edit settings when
|
||||
- I am a super user
|
||||
- I can clear settings when
|
||||
- I am a super user
|
||||
'''
|
||||
|
||||
model = TowerSettings
|
||||
|
||||
def get_queryset(self):
|
||||
if self.user.is_superuser:
|
||||
return self.model.objects.all()
|
||||
return self.model.objects.none()
|
||||
|
||||
def can_change(self, obj, data):
|
||||
return self.user.is_superuser
|
||||
|
||||
def can_delete(self, obj):
|
||||
return self.user.is_superuser
|
||||
|
||||
register_access(User, UserAccess)
|
||||
register_access(Organization, OrganizationAccess)
|
||||
register_access(Inventory, InventoryAccess)
|
||||
@ -1661,3 +1689,4 @@ register_access(UnifiedJobTemplate, UnifiedJobTemplateAccess)
|
||||
register_access(UnifiedJob, UnifiedJobAccess)
|
||||
register_access(ActivityStream, ActivityStreamAccess)
|
||||
register_access(CustomInventoryScript, CustomInventoryScriptAccess)
|
||||
register_access(TowerSettings, TowerSettingsAccess)
|
||||
|
51
awx/main/conf.py
Normal file
51
awx/main/conf.py
Normal file
@ -0,0 +1,51 @@
|
||||
# Copyright (c) 2015 Ansible, Inc..
|
||||
# All Rights Reserved.
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings as django_settings
|
||||
from django.db.utils import ProgrammingError
|
||||
from django.db import OperationalError
|
||||
from awx.main.models.configuration import TowerSettings
|
||||
|
||||
logger = logging.getLogger('awx.main.conf')
|
||||
|
||||
class TowerConfiguration(object):
|
||||
|
||||
# TODO: Caching so we don't have to hit the database every time for settings
|
||||
def __getattr__(self, key):
|
||||
settings_manifest = django_settings.TOWER_SETTINGS_MANIFEST
|
||||
if key not in settings_manifest:
|
||||
raise AttributeError("Tower Setting with key '{0}' is not defined in the manifest".format(key))
|
||||
default_value = settings_manifest[key]['default']
|
||||
ts = TowerSettings.objects.filter(key=key)
|
||||
try:
|
||||
if not ts.exists():
|
||||
try:
|
||||
val_actual = getattr(django_settings, key)
|
||||
except AttributeError:
|
||||
val_actual = default_value
|
||||
return val_actual
|
||||
return ts[0].value_converted
|
||||
except (ProgrammingError, OperationalError), e:
|
||||
# Database is not available yet, usually during migrations so lets use the default
|
||||
logger.debug("Database settings not available yet, using defaults ({0})".format(e))
|
||||
return default_value
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
settings_manifest = django_settings.TOWER_SETTINGS_MANIFEST
|
||||
if key not in settings_manifest:
|
||||
raise AttributeError("Tower Setting with key '{0}' does not exist".format(key))
|
||||
settings_entry = settings_manifest[key]
|
||||
settings_actual = TowerSettings.objects.filter(key=key)
|
||||
if not settings_actual.exists():
|
||||
settings_actual = TowerSettings(key=key,
|
||||
description=settings_entry['description'],
|
||||
category=settings_entry['category'],
|
||||
value=value,
|
||||
value_type=settings_entry['type'])
|
||||
else:
|
||||
settings_actual['value'] = value
|
||||
settings_actual.save()
|
||||
|
||||
tower_settings = TowerConfiguration()
|
@ -28,6 +28,7 @@ from awx.main.models import * # noqa
|
||||
from awx.main.utils import ignore_inventory_computed_fields, check_proot_installed, wrap_args_with_proot
|
||||
from awx.main.signals import disable_activity_stream
|
||||
from awx.main.task_engine import TaskSerializer as LicenseReader
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
logger = logging.getLogger('awx.main.commands.inventory_import')
|
||||
|
||||
@ -356,7 +357,7 @@ class ExecutableJsonLoader(BaseLoader):
|
||||
data = {}
|
||||
stdout, stderr = '', ''
|
||||
try:
|
||||
if self.is_custom and getattr(settings, 'AWX_PROOT_ENABLED', False):
|
||||
if self.is_custom and getattr(tower_settings, 'AWX_PROOT_ENABLED', False):
|
||||
if not check_proot_installed():
|
||||
raise RuntimeError("proot is not installed but is configured for use")
|
||||
kwargs = {'proot_temp_dir': self.source_dir} # TODO: Remove proot dir
|
||||
|
@ -11,10 +11,10 @@ from django.db import IntegrityError
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.functional import curry
|
||||
from django.conf import settings
|
||||
|
||||
from awx import __version__ as version
|
||||
from awx.main.models import ActivityStream, Instance
|
||||
from awx.main.conf import tower_settings
|
||||
from awx.api.authentication import TokenAuthentication
|
||||
|
||||
|
||||
@ -117,6 +117,6 @@ class AuthTokenTimeoutMiddleware(object):
|
||||
if not TokenAuthentication._get_x_auth_token_header(request):
|
||||
return response
|
||||
|
||||
response['Auth-Token-Timeout'] = int(settings.AUTH_TOKEN_EXPIRATION)
|
||||
response['Auth-Token-Timeout'] = int(tower_settings.AUTH_TOKEN_EXPIRATION)
|
||||
return response
|
||||
|
||||
|
557
awx/main/migrations/0075_v300_changes.py
Normal file
557
awx/main/migrations/0075_v300_changes.py
Normal file
@ -0,0 +1,557 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'TowerSettings'
|
||||
db.create_table(u'main_towersettings', (
|
||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('created', self.gf('django.db.models.fields.DateTimeField')(default=None)),
|
||||
('modified', self.gf('django.db.models.fields.DateTimeField')(default=None)),
|
||||
('key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
|
||||
('description', self.gf('django.db.models.fields.TextField')()),
|
||||
('category', self.gf('django.db.models.fields.CharField')(max_length=128)),
|
||||
('value', self.gf('django.db.models.fields.TextField')()),
|
||||
('value_type', self.gf('django.db.models.fields.CharField')(max_length=12)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='settings', null=True, to=orm['auth.User'])),
|
||||
))
|
||||
db.send_create_signal('main', ['TowerSettings'])
|
||||
|
||||
# Adding M2M table for field tower_settings on 'ActivityStream'
|
||||
m2m_table_name = db.shorten_name(u'main_activitystream_tower_settings')
|
||||
db.create_table(m2m_table_name, (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('activitystream', models.ForeignKey(orm['main.activitystream'], null=False)),
|
||||
('towersettings', models.ForeignKey(orm['main.towersettings'], null=False))
|
||||
))
|
||||
db.create_unique(m2m_table_name, ['activitystream_id', 'towersettings_id'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'TowerSettings'
|
||||
db.delete_table(u'main_towersettings')
|
||||
|
||||
# Removing M2M table for field tower_settings on 'ActivityStream'
|
||||
db.delete_table(db.shorten_name(u'main_activitystream_tower_settings'))
|
||||
|
||||
|
||||
models = {
|
||||
u'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
u'auth.permission': {
|
||||
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
u'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
u'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'main.activitystream': {
|
||||
'Meta': {'object_name': 'ActivityStream'},
|
||||
'actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_stream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'ad_hoc_command': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.AdHocCommand']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'changes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'credential': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Credential']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'custom_inventory_script': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.CustomInventoryScript']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'host': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Host']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Inventory']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'inventory_source': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventorySource']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'inventory_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventoryUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'job': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Job']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'job_template': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.JobTemplate']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'object1': ('django.db.models.fields.TextField', [], {}),
|
||||
'object2': ('django.db.models.fields.TextField', [], {}),
|
||||
'object_relationship_type': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'operation': ('django.db.models.fields.CharField', [], {'max_length': '13'}),
|
||||
'organization': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Organization']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'permission': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'project': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Project']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'project_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.ProjectUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'schedule': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Schedule']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'team': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Team']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'tower_settings': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.TowerSettings']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'unified_job': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job+'", 'blank': 'True', 'to': "orm['main.UnifiedJob']"}),
|
||||
'unified_job_template': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job_template+'", 'blank': 'True', 'to': "orm['main.UnifiedJobTemplate']"}),
|
||||
'user': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'main.adhoccommand': {
|
||||
'Meta': {'object_name': 'AdHocCommand', '_ormbases': ['main.UnifiedJob']},
|
||||
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'credential': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
|
||||
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ad_hoc_commands'", 'symmetrical': 'False', 'through': "orm['main.AdHocCommandEvent']", 'to': "orm['main.Host']"}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
|
||||
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'module_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'module_name': ('django.db.models.fields.CharField', [], {'default': "'command'", 'max_length': '1024', 'blank': 'True'}),
|
||||
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
|
||||
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||
},
|
||||
'main.adhoccommandevent': {
|
||||
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('ad_hoc_command', 'host_name')]", 'object_name': 'AdHocCommandEvent'},
|
||||
'ad_hoc_command': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_command_events'", 'to': "orm['main.AdHocCommand']"}),
|
||||
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'host': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_command_events'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
|
||||
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'})
|
||||
},
|
||||
'main.authtoken': {
|
||||
'Meta': {'object_name': 'AuthToken'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'expires': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'request_hash': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': u"orm['auth.User']"})
|
||||
},
|
||||
'main.credential': {
|
||||
'Meta': {'ordering': "('kind', 'name')", 'unique_together': "[('user', 'team', 'kind', 'name')]", 'object_name': 'Credential'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'become_method': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||
'become_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'become_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'cloud': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'kind': ('django.db.models.fields.CharField', [], {'default': "'ssh'", 'max_length': '32'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'project': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||
'security_token': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'team': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': "orm['main.Team']"}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': u"orm['auth.User']"}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'vault_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
|
||||
},
|
||||
'main.custominventoryscript': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'CustomInventoryScript'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'custom_inventory_scripts'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
|
||||
'script': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||
},
|
||||
'main.group': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
|
||||
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
|
||||
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
|
||||
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||
},
|
||||
'main.host': {
|
||||
'Meta': {'ordering': "('inventory', 'name')", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'instance_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
|
||||
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'hosts'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
|
||||
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'hosts_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Job']"}),
|
||||
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||
},
|
||||
'main.instance': {
|
||||
'Meta': {'object_name': 'Instance'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'})
|
||||
},
|
||||
'main.inventory': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'Inventory'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'inventory_sources_with_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
|
||||
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'total_inventory_sources': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||
},
|
||||
'main.inventorysource': {
|
||||
'Meta': {'object_name': 'InventorySource', '_ormbases': ['main.UnifiedJobTemplate']},
|
||||
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventorysources'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||
'group': ('awx.main.fields.AutoOneToOneField', [], {'default': 'None', 'related_name': "'inventory_source'", 'unique': 'True', 'null': 'True', 'to': "orm['main.Group']"}),
|
||||
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'inventory_sources'", 'null': 'True', 'to': "orm['main.Inventory']"}),
|
||||
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
|
||||
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
|
||||
'update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'main.inventoryupdate': {
|
||||
'Meta': {'object_name': 'InventoryUpdate', '_ormbases': ['main.UnifiedJob']},
|
||||
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventoryupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'inventory_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventory_updates'", 'to': "orm['main.InventorySource']"}),
|
||||
'license_error': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
|
||||
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'main.job': {
|
||||
'Meta': {'ordering': "('id',)", 'object_name': 'Job', '_ormbases': ['main.UnifiedJob']},
|
||||
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'jobs'", 'symmetrical': 'False', 'through': "orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
|
||||
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
|
||||
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
|
||||
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
|
||||
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||
},
|
||||
'main.jobevent': {
|
||||
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
|
||||
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'host': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'job_events_as_primary_host'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
|
||||
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'job_events'", 'symmetrical': 'False', 'to': "orm['main.Host']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.JobEvent']"}),
|
||||
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||
'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'})
|
||||
},
|
||||
'main.jobhostsummary': {
|
||||
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host_name')]", 'object_name': 'JobHostSummary'},
|
||||
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'host': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'job_host_summaries'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
|
||||
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
|
||||
},
|
||||
'main.joborigin': {
|
||||
'Meta': {'object_name': 'JobOrigin'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Instance']"}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'unified_job': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'job_origin'", 'unique': 'True', 'to': "orm['main.UnifiedJob']"})
|
||||
},
|
||||
'main.jobtemplate': {
|
||||
'Meta': {'ordering': "('name',)", 'object_name': 'JobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
|
||||
'ask_variables_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
|
||||
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
|
||||
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'survey_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'survey_spec': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
|
||||
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||
},
|
||||
'main.organization': {
|
||||
'Meta': {'ordering': "('name',)", 'object_name': 'Organization'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': "orm['main.Project']"}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
|
||||
},
|
||||
'main.permission': {
|
||||
'Meta': {'object_name': 'Permission'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Project']"}),
|
||||
'run_ad_hoc_commands': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
|
||||
},
|
||||
'main.profile': {
|
||||
'Meta': {'object_name': 'Profile'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ldap_dn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'user': ('awx.main.fields.AutoOneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"})
|
||||
},
|
||||
'main.project': {
|
||||
'Meta': {'ordering': "('id',)", 'object_name': 'Project', '_ormbases': ['main.UnifiedJobTemplate']},
|
||||
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
|
||||
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
|
||||
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
|
||||
'scm_update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'main.projectupdate': {
|
||||
'Meta': {'object_name': 'ProjectUpdate', '_ormbases': ['main.UnifiedJob']},
|
||||
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projectupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': "orm['main.Project']"}),
|
||||
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
|
||||
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
|
||||
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'main.schedule': {
|
||||
'Meta': {'ordering': "['-next_run']", 'object_name': 'Schedule'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'dtend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||
'dtstart': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'extra_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||
'next_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||
'rrule': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'schedules'", 'to': "orm['main.UnifiedJobTemplate']"})
|
||||
},
|
||||
'main.systemjob': {
|
||||
'Meta': {'ordering': "('id',)", 'object_name': 'SystemJob', '_ormbases': ['main.UnifiedJob']},
|
||||
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||
'system_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.SystemJobTemplate']", 'blank': 'True', 'null': 'True'}),
|
||||
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'main.systemjobtemplate': {
|
||||
'Meta': {'object_name': 'SystemJobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
|
||||
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'main.team': {
|
||||
'Meta': {'ordering': "('organization__name', 'name')", 'unique_together': "[('organization', 'name')]", 'object_name': 'Team'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
|
||||
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': "orm['main.Project']"}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
|
||||
},
|
||||
'main.towersettings': {
|
||||
'Meta': {'object_name': 'TowerSettings'},
|
||||
'category': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'settings'", 'null': 'True', 'to': u"orm['auth.User']"}),
|
||||
'value': ('django.db.models.fields.TextField', [], {}),
|
||||
'value_type': ('django.db.models.fields.CharField', [], {'max_length': '12'})
|
||||
},
|
||||
'main.unifiedjob': {
|
||||
'Meta': {'object_name': 'UnifiedJob'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'dependent_jobs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'dependent_jobs_rel_+'", 'to': "orm['main.UnifiedJob']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'elapsed': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '3'}),
|
||||
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'finished': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'job_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||
'job_explanation': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
|
||||
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjob_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
|
||||
'result_stdout_file': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'result_stdout_text': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.Schedule']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||
'start_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'started': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
|
||||
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjob_unified_jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJobTemplate']"})
|
||||
},
|
||||
'main.unifiedjobtemplate': {
|
||||
'Meta': {'unique_together': "[('polymorphic_ctype', 'name')]", 'object_name': 'UnifiedJobTemplate'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'current_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_current_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'has_schedules': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
|
||||
'last_job_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'next_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||
'next_schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_next_schedule+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Schedule']"}),
|
||||
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
|
||||
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjobtemplate_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'ok'", 'max_length': '32'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['main']
|
@ -16,6 +16,7 @@ from awx.main.models.ad_hoc_commands import * # noqa
|
||||
from awx.main.models.schedules import * # noqa
|
||||
from awx.main.models.activity_stream import * # noqa
|
||||
from awx.main.models.ha import * # noqa
|
||||
from awx.main.models.configuration import * # noqa
|
||||
|
||||
# Monkeypatch Django serializer to ignore django-taggit fields (which break
|
||||
# the dumpdata command; see https://github.com/alex/django-taggit/issues/155).
|
||||
@ -58,3 +59,4 @@ activity_stream_registrar.connect(AdHocCommand)
|
||||
# activity_stream_registrar.connect(Profile)
|
||||
activity_stream_registrar.connect(Schedule)
|
||||
activity_stream_registrar.connect(CustomInventoryScript)
|
||||
activity_stream_registrar.connect(TowerSettings)
|
||||
|
@ -53,6 +53,7 @@ class ActivityStream(models.Model):
|
||||
ad_hoc_command = models.ManyToManyField("AdHocCommand", blank=True)
|
||||
schedule = models.ManyToManyField("Schedule", blank=True)
|
||||
custom_inventory_script = models.ManyToManyField("CustomInventoryScript", blank=True)
|
||||
tower_settings = models.ManyToManyField("TowerSettings", blank=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('api:activity_stream_detail', args=(self.pk,))
|
||||
|
@ -21,6 +21,7 @@ from jsonfield import JSONField
|
||||
from awx.main.models.base import * # noqa
|
||||
from awx.main.models.unified_jobs import * # noqa
|
||||
from awx.main.utils import decrypt_field
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
logger = logging.getLogger('awx.main.models.ad_hoc_commands')
|
||||
|
||||
@ -29,8 +30,8 @@ __all__ = ['AdHocCommand', 'AdHocCommandEvent']
|
||||
|
||||
class AdHocCommand(UnifiedJob):
|
||||
|
||||
MODULE_NAME_CHOICES = [(x,x) for x in settings.AD_HOC_COMMANDS]
|
||||
MODULE_NAME_DEFAULT = 'command' if 'command' in settings.AD_HOC_COMMANDS else None
|
||||
MODULE_NAME_CHOICES = [(x,x) for x in tower_settings.AD_HOC_COMMANDS]
|
||||
MODULE_NAME_DEFAULT = 'command' if 'command' in tower_settings.AD_HOC_COMMANDS else None
|
||||
|
||||
class Meta(object):
|
||||
app_label = 'main'
|
||||
@ -104,7 +105,7 @@ class AdHocCommand(UnifiedJob):
|
||||
if type(self.module_name) not in (str, unicode):
|
||||
raise ValidationError("Invalid type for ad hoc command")
|
||||
module_name = self.module_name.strip() or 'command'
|
||||
if module_name not in settings.AD_HOC_COMMANDS:
|
||||
if module_name not in tower_settings.AD_HOC_COMMANDS:
|
||||
raise ValidationError('Unsupported module for ad hoc commands.')
|
||||
return module_name
|
||||
|
||||
|
62
awx/main/models/configuration.py
Normal file
62
awx/main/models/configuration.py
Normal file
@ -0,0 +1,62 @@
|
||||
# Copyright (c) 2015 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import json
|
||||
|
||||
# Django
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
# Tower
|
||||
from awx.main.models.base import CreatedModifiedModel
|
||||
|
||||
class TowerSettings(CreatedModifiedModel):
|
||||
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
|
||||
SETTINGS_TYPE_CHOICES = [
|
||||
('string', _("String")),
|
||||
('int', _('Integer')),
|
||||
('float', _('Decimal')),
|
||||
('json', _('JSON')),
|
||||
('bool', _('Boolean')),
|
||||
('password', _('Password')),
|
||||
('list', _('List'))
|
||||
]
|
||||
|
||||
key = models.CharField(
|
||||
max_length=255,
|
||||
unique=True
|
||||
)
|
||||
description = models.TextField()
|
||||
category = models.CharField(max_length=128)
|
||||
value = models.TextField()
|
||||
value_type = models.CharField(
|
||||
max_length=12,
|
||||
choices=SETTINGS_TYPE_CHOICES
|
||||
)
|
||||
user = models.ForeignKey(
|
||||
'auth.User',
|
||||
related_name='settings',
|
||||
default=None,
|
||||
null=True,
|
||||
editable=False,
|
||||
)
|
||||
|
||||
@property
|
||||
def value_converted(self):
|
||||
if self.value_type == 'json':
|
||||
converted_type = json.loads(self.value)
|
||||
elif self.value_type == 'password':
|
||||
converted_type = self.value
|
||||
elif self.value_type == 'list':
|
||||
converted_type = [x.strip() for x in self.value.split(',')]
|
||||
elif self.value_type == 'bool':
|
||||
converted_type = self.value in [True, "true", "True", 1, "1", "yes"]
|
||||
elif self.value_type == 'string':
|
||||
converted_type = self.value
|
||||
else:
|
||||
t = __builtins__[self.value_type]
|
||||
converted_type = t(self.value)
|
||||
return converted_type
|
@ -24,6 +24,7 @@ from awx.main.models.unified_jobs import * # noqa
|
||||
from awx.main.utils import decrypt_field, ignore_inventory_computed_fields
|
||||
from awx.main.utils import emit_websocket_notification
|
||||
from awx.main.redact import PlainTextCleaner
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
logger = logging.getLogger('awx.main.models.jobs')
|
||||
|
||||
@ -318,9 +319,9 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
|
||||
|
||||
@property
|
||||
def cache_timeout_blocked(self):
|
||||
if Job.objects.filter(job_template=self, status__in=['pending', 'waiting', 'running']).count() > getattr(settings, 'SCHEDULE_MAX_JOBS', 10):
|
||||
if Job.objects.filter(job_template=self, status__in=['pending', 'waiting', 'running']).count() > getattr(tower_settings, 'SCHEDULE_MAX_JOBS', 10):
|
||||
logger.error("Job template %s could not be started because there are more than %s other jobs from that template waiting to run" %
|
||||
(self.name, getattr(settings, 'SCHEDULE_MAX_JOBS', 10)))
|
||||
(self.name, getattr(tower_settings, 'SCHEDULE_MAX_JOBS', 10)))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -18,6 +18,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
# AWX
|
||||
from awx.main.fields import AutoOneToOneField
|
||||
from awx.main.models.base import * # noqa
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
__all__ = ['Organization', 'Team', 'Permission', 'Profile', 'AuthToken']
|
||||
|
||||
@ -242,7 +243,7 @@ class AuthToken(BaseModel):
|
||||
if not now:
|
||||
now = tz_now()
|
||||
if not self.pk or not self.is_expired(now=now):
|
||||
self.expires = now + datetime.timedelta(seconds=settings.AUTH_TOKEN_EXPIRATION)
|
||||
self.expires = now + datetime.timedelta(seconds=tower_settings.AUTH_TOKEN_EXPIRATION)
|
||||
if save:
|
||||
self.save()
|
||||
|
||||
@ -259,12 +260,12 @@ class AuthToken(BaseModel):
|
||||
if now is None:
|
||||
now = tz_now()
|
||||
invalid_tokens = AuthToken.objects.none()
|
||||
if settings.AUTH_TOKEN_PER_USER != -1:
|
||||
if tower_settings.AUTH_TOKEN_PER_USER != -1:
|
||||
invalid_tokens = AuthToken.objects.filter(
|
||||
user=user,
|
||||
expires__gt=now,
|
||||
reason='',
|
||||
).order_by('-created')[settings.AUTH_TOKEN_PER_USER:]
|
||||
).order_by('-created')[tower_settings.AUTH_TOKEN_PER_USER:]
|
||||
return invalid_tokens
|
||||
|
||||
def generate_key(self):
|
||||
@ -293,7 +294,7 @@ class AuthToken(BaseModel):
|
||||
valid_n_tokens_qs = self.user.auth_tokens.filter(
|
||||
expires__gt=now,
|
||||
reason='',
|
||||
).order_by('-created')[0:settings.AUTH_TOKEN_PER_USER]
|
||||
).order_by('-created')[0:tower_settings.AUTH_TOKEN_PER_USER]
|
||||
valid_n_tokens = valid_n_tokens_qs.values_list('key', flat=True)
|
||||
|
||||
return bool(self.key in valid_n_tokens)
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models.signals import pre_save, post_save, post_delete, m2m_changed
|
||||
|
||||
logger = logging.getLogger('awx.main.registrar')
|
||||
@ -14,7 +13,8 @@ class ActivityStreamRegistrar(object):
|
||||
self.models = []
|
||||
|
||||
def connect(self, model):
|
||||
if not getattr(settings, 'ACTIVITY_STREAM_ENABLED', True):
|
||||
from awx.main.conf import tower_settings
|
||||
if not getattr(tower_settings, 'ACTIVITY_STREAM_ENABLED', True):
|
||||
return
|
||||
from awx.main.signals import activity_stream_create, activity_stream_update, activity_stream_delete, activity_stream_associate
|
||||
|
||||
|
@ -8,7 +8,6 @@ import threading
|
||||
import json
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||
from django.dispatch import receiver
|
||||
|
||||
@ -22,6 +21,7 @@ from awx.api.serializers import * # noqa
|
||||
from awx.main.utils import model_instance_diff, model_to_dict, camelcase_to_underscore, emit_websocket_notification
|
||||
from awx.main.utils import ignore_inventory_computed_fields, ignore_inventory_group_removal, _inventory_updates
|
||||
from awx.main.tasks import update_inventory_computed_fields
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
__all__ = []
|
||||
|
||||
@ -273,7 +273,7 @@ def update_host_last_job_after_job_deleted(sender, **kwargs):
|
||||
|
||||
class ActivityStreamEnabled(threading.local):
|
||||
def __init__(self):
|
||||
self.enabled = getattr(settings, 'ACTIVITY_STREAM_ENABLED', True)
|
||||
self.enabled = getattr(tower_settings, 'ACTIVITY_STREAM_ENABLED', True)
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.enabled)
|
||||
@ -306,6 +306,7 @@ model_serializer_mapping = {
|
||||
JobTemplate: JobTemplateSerializer,
|
||||
Job: JobSerializer,
|
||||
AdHocCommand: AdHocCommandSerializer,
|
||||
TowerSettings: TowerSettingsSerializer,
|
||||
}
|
||||
|
||||
def activity_stream_create(sender, instance, created, **kwargs):
|
||||
@ -320,6 +321,10 @@ def activity_stream_create(sender, instance, created, **kwargs):
|
||||
object1=object1,
|
||||
changes=json.dumps(model_to_dict(instance, model_serializer_mapping)))
|
||||
activity_entry.save()
|
||||
#TODO: Weird situation where cascade SETNULL doesn't work
|
||||
# it might actually be a good idea to remove all of these FK references since
|
||||
# we don't really use them anyway.
|
||||
if type(instance) is not TowerSettings:
|
||||
getattr(activity_entry, object1).add(instance)
|
||||
|
||||
def activity_stream_update(sender, instance, **kwargs):
|
||||
@ -347,6 +352,7 @@ def activity_stream_update(sender, instance, **kwargs):
|
||||
object1=object1,
|
||||
changes=json.dumps(changes))
|
||||
activity_entry.save()
|
||||
if type(instance) is not TowerSettings:
|
||||
getattr(activity_entry, object1).add(instance)
|
||||
|
||||
def activity_stream_delete(sender, instance, **kwargs):
|
||||
|
@ -44,6 +44,7 @@ from django.utils.timezone import now
|
||||
from awx.main.constants import CLOUD_PROVIDERS
|
||||
from awx.main.models import * # noqa
|
||||
from awx.main.queue import FifoQueue
|
||||
from awx.main.conf import tower_settings
|
||||
from awx.main.utils import (get_ansible_version, get_ssh_version, decrypt_field, update_scm_url,
|
||||
ignore_inventory_computed_fields, emit_websocket_notification,
|
||||
check_proot_installed, build_proot_temp_dir, wrap_args_with_proot)
|
||||
@ -348,7 +349,7 @@ class BaseTask(Task):
|
||||
python_paths.insert(0, local_site_packages)
|
||||
env['PYTHONPATH'] = os.pathsep.join(python_paths)
|
||||
if self.should_use_proot:
|
||||
env['PROOT_TMP_DIR'] = settings.AWX_PROOT_BASE_PATH
|
||||
env['PROOT_TMP_DIR'] = tower_settings.AWX_PROOT_BASE_PATH
|
||||
return env
|
||||
|
||||
def build_safe_env(self, instance, **kwargs):
|
||||
@ -461,7 +462,7 @@ class BaseTask(Task):
|
||||
instance = self.update_model(instance.pk)
|
||||
if instance.cancel_flag:
|
||||
try:
|
||||
if settings.AWX_PROOT_ENABLED:
|
||||
if tower_settings.AWX_PROOT_ENABLED:
|
||||
# NOTE: Refactor this once we get a newer psutil across the board
|
||||
if not psutil:
|
||||
os.kill(child.pid, signal.SIGKILL)
|
||||
@ -654,9 +655,9 @@ class RunJob(BaseTask):
|
||||
'''
|
||||
plugin_dir = self.get_path_to('..', 'plugins', 'callback')
|
||||
plugin_dirs = [plugin_dir]
|
||||
if hasattr(settings, 'AWX_ANSIBLE_CALLBACK_PLUGINS') and \
|
||||
settings.AWX_ANSIBLE_CALLBACK_PLUGINS:
|
||||
plugin_dirs.append(settings.AWX_ANSIBLE_CALLBACK_PLUGINS)
|
||||
if hasattr(tower_settings, 'AWX_ANSIBLE_CALLBACK_PLUGINS') and \
|
||||
tower_settings.AWX_ANSIBLE_CALLBACK_PLUGINS:
|
||||
plugin_dirs.append(tower_settings.AWX_ANSIBLE_CALLBACK_PLUGINS)
|
||||
plugin_path = ':'.join(plugin_dirs)
|
||||
env = super(RunJob, self).build_env(job, **kwargs)
|
||||
# Set environment variables needed for inventory and job event
|
||||
@ -850,7 +851,7 @@ class RunJob(BaseTask):
|
||||
'''
|
||||
Return whether this task should use proot.
|
||||
'''
|
||||
return getattr(settings, 'AWX_PROOT_ENABLED', False)
|
||||
return getattr(tower_settings, 'AWX_PROOT_ENABLED', False)
|
||||
|
||||
def pre_run_hook(self, job, **kwargs):
|
||||
if job.job_type == PERM_INVENTORY_SCAN:
|
||||
@ -1475,7 +1476,7 @@ class RunAdHocCommand(BaseTask):
|
||||
'''
|
||||
Return whether this task should use proot.
|
||||
'''
|
||||
return getattr(settings, 'AWX_PROOT_ENABLED', False)
|
||||
return getattr(tower_settings, 'AWX_PROOT_ENABLED', False)
|
||||
|
||||
def post_run_hook(self, ad_hoc_command, **kwargs):
|
||||
'''
|
||||
|
@ -19,3 +19,4 @@ from awx.main.tests.commands import * # noqa
|
||||
from awx.main.tests.fact import * # noqa
|
||||
from awx.main.tests.unified_jobs import * # noqa
|
||||
from awx.main.tests.ha import * # noqa
|
||||
from awx.main.tests.settings import * # noqa
|
||||
|
105
awx/main/tests/settings.py
Normal file
105
awx/main/tests/settings.py
Normal file
@ -0,0 +1,105 @@
|
||||
# Copyright (c) 2016 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from awx.main.tests.base import BaseTest
|
||||
from awx.main.models import * # noqa
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.utils import override_settings
|
||||
|
||||
TEST_TOWER_SETTINGS_MANIFEST = {
|
||||
"TEST_SETTING_INT": {
|
||||
"name": "An Integer Field",
|
||||
"description": "An Integer Field",
|
||||
"default": 1,
|
||||
"type": "int",
|
||||
"category": "test"
|
||||
},
|
||||
"TEST_SETTING_STRING": {
|
||||
"name": "A String Field",
|
||||
"description": "A String Field",
|
||||
"default": "test",
|
||||
"type": "string",
|
||||
"category": "test"
|
||||
},
|
||||
"TEST_SETTING_BOOL": {
|
||||
"name": "A Bool Field",
|
||||
"description": "A Bool Field",
|
||||
"default": True,
|
||||
"type": "bool",
|
||||
"category": "test"
|
||||
},
|
||||
"TEST_SETTING_LIST": {
|
||||
"name": "A List Field",
|
||||
"description": "A List Field",
|
||||
"default": ["A", "Simple", "List"],
|
||||
"type": "list",
|
||||
"category": "test"
|
||||
}
|
||||
}
|
||||
|
||||
@override_settings(TOWER_SETTINGS_MANIFEST=TEST_TOWER_SETTINGS_MANIFEST)
|
||||
class SettingsTest(BaseTest):
|
||||
|
||||
def setUp(self):
|
||||
super(SettingsTest, self).setUp()
|
||||
self.setup_instances()
|
||||
self.setup_users()
|
||||
|
||||
def get_settings(self, expected_count=4):
|
||||
result = self.get(reverse('api:settings_list'), expect=200)
|
||||
self.assertEqual(result['count'], expected_count)
|
||||
return result['results']
|
||||
|
||||
def get_individual_setting(self, setting):
|
||||
all_settings = self.get_settings()
|
||||
setting_actual = None
|
||||
for setting_item in all_settings:
|
||||
if setting_item['key'] == setting:
|
||||
setting_actual = setting_item
|
||||
break
|
||||
self.assertIsNotNone(setting_actual)
|
||||
return setting_actual
|
||||
|
||||
def set_setting(self, key, value):
|
||||
self.post(reverse('api:settings_list'), data={"key": key, "value": value}, expect=201)
|
||||
|
||||
def test_get_settings(self):
|
||||
# Regular user should see nothing (no user settings yet)
|
||||
with self.current_user(self.normal_django_user):
|
||||
self.get_settings(expected_count=0)
|
||||
# anonymous user should get a 401
|
||||
self.get(reverse('api:settings_list'), expect=401)
|
||||
# super user can see everything
|
||||
with self.current_user(self.super_django_user):
|
||||
self.get_settings(expected_count=len(TEST_TOWER_SETTINGS_MANIFEST))
|
||||
|
||||
def test_set_and_reset_settings(self):
|
||||
settings_reset = reverse('api:settings_reset')
|
||||
with self.current_user(self.super_django_user):
|
||||
# Set and reset a single setting
|
||||
setting_int = self.get_individual_setting('TEST_SETTING_INT')
|
||||
self.assertEqual(setting_int['value'], TEST_TOWER_SETTINGS_MANIFEST['TEST_SETTING_INT']['default'])
|
||||
self.set_setting('TEST_SETTING_INT', 2)
|
||||
setting_int = self.get_individual_setting('TEST_SETTING_INT')
|
||||
self.assertEqual(setting_int['value'], 2)
|
||||
self.post(settings_reset, data={"key": 'TEST_SETTING_INT'}, expect=204)
|
||||
setting_int = self.get_individual_setting('TEST_SETTING_INT')
|
||||
self.assertEqual(setting_int['value'], TEST_TOWER_SETTINGS_MANIFEST['TEST_SETTING_INT']['default'])
|
||||
|
||||
def test_clear_all_settings(self):
|
||||
settings_list = reverse('api:settings_list')
|
||||
with self.current_user(self.super_django_user):
|
||||
self.set_setting('TEST_SETTING_INT', 2)
|
||||
self.set_setting('TEST_SETTING_STRING', "foo")
|
||||
self.set_setting('TEST_SETTING_BOOL', False)
|
||||
self.set_setting('TEST_SETTING_LIST', [1,2,3])
|
||||
all_settings = self.get_settings()
|
||||
for setting_entry in all_settings:
|
||||
self.assertNotEqual(setting_entry['value'],
|
||||
TEST_TOWER_SETTINGS_MANIFEST[setting_entry['key']]['default'])
|
||||
self.delete(settings_list, expect=200)
|
||||
all_settings = self.get_settings()
|
||||
for setting_entry in all_settings:
|
||||
self.assertEqual(setting_entry['value'],
|
||||
TEST_TOWER_SETTINGS_MANIFEST[setting_entry['key']]['default'])
|
@ -16,6 +16,7 @@ from django.test.utils import override_settings
|
||||
# AWX
|
||||
from awx.main.models import * # noqa
|
||||
from awx.main.tests.base import BaseTest
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
__all__ = ['AuthTokenTimeoutTest', 'AuthTokenLimitTest', 'AuthTokenProxyTest', 'UsersTest', 'LdapTest']
|
||||
|
||||
@ -38,7 +39,7 @@ class AuthTokenTimeoutTest(BaseTest):
|
||||
|
||||
response = self._generic_rest(dashboard_url, expect=200, method='get', return_response_object=True, client_kwargs=kwargs)
|
||||
self.assertIn('Auth-Token-Timeout', response)
|
||||
self.assertEqual(response['Auth-Token-Timeout'], str(settings.AUTH_TOKEN_EXPIRATION))
|
||||
self.assertEqual(response['Auth-Token-Timeout'], str(tower_settings.AUTH_TOKEN_EXPIRATION))
|
||||
|
||||
class AuthTokenLimitTest(BaseTest):
|
||||
def setUp(self):
|
||||
|
@ -448,8 +448,8 @@ def build_proot_temp_dir():
|
||||
'''
|
||||
Create a temporary directory for proot to use.
|
||||
'''
|
||||
from django.conf import settings
|
||||
path = tempfile.mkdtemp(prefix='ansible_tower_proot_', dir=settings.AWX_PROOT_BASE_PATH)
|
||||
from awx.main.conf import tower_settings
|
||||
path = tempfile.mkdtemp(prefix='ansible_tower_proot_', dir=tower_settings.AWX_PROOT_BASE_PATH)
|
||||
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
||||
return path
|
||||
|
||||
@ -462,13 +462,14 @@ def wrap_args_with_proot(args, cwd, **kwargs):
|
||||
- /var/log/supervisor
|
||||
- /tmp (except for own tmp files)
|
||||
'''
|
||||
from awx.main.conf import tower_settings
|
||||
from django.conf import settings
|
||||
new_args = [getattr(settings, 'AWX_PROOT_CMD', 'proot'), '-v',
|
||||
str(getattr(settings, 'AWX_PROOT_VERBOSITY', '0')), '-r', '/']
|
||||
hide_paths = ['/etc/tower', '/var/lib/awx', '/var/log',
|
||||
tempfile.gettempdir(), settings.PROJECTS_ROOT,
|
||||
settings.JOBOUTPUT_ROOT]
|
||||
hide_paths.extend(getattr(settings, 'AWX_PROOT_HIDE_PATHS', None) or [])
|
||||
hide_paths.extend(getattr(tower_settings, 'AWX_PROOT_HIDE_PATHS', None) or [])
|
||||
for path in sorted(set(hide_paths)):
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
@ -484,7 +485,7 @@ def wrap_args_with_proot(args, cwd, **kwargs):
|
||||
show_paths = [cwd, kwargs['private_data_dir']]
|
||||
else:
|
||||
show_paths = [cwd]
|
||||
show_paths.extend(getattr(settings, 'AWX_PROOT_SHOW_PATHS', None) or [])
|
||||
show_paths.extend(getattr(tower_settings, 'AWX_PROOT_SHOW_PATHS', None) or [])
|
||||
for path in sorted(set(show_paths)):
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
|
@ -262,11 +262,11 @@ SERVER_EMAIL = 'root@localhost'
|
||||
|
||||
# Default email address to use for various automated correspondence from
|
||||
# the site managers.
|
||||
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
|
||||
DEFAULT_FROM_EMAIL = 'tower@localhost'
|
||||
|
||||
# Subject-line prefix for email messages send with django.core.mail.mail_admins
|
||||
# or ...mail_managers. Make sure to include the trailing space.
|
||||
EMAIL_SUBJECT_PREFIX = '[AWX] '
|
||||
EMAIL_SUBJECT_PREFIX = '[Tower] '
|
||||
|
||||
# The email backend to use. For possible shortcuts see django.core.mail.
|
||||
# The default is to use the SMTP backend.
|
||||
@ -688,6 +688,141 @@ FACT_CACHE_PORT = 6564
|
||||
|
||||
ORG_ADMINS_CAN_SEE_ALL_USERS = True
|
||||
|
||||
TOWER_SETTINGS_MANIFEST = {
|
||||
"SCHEDULE_MAX_JOBS": {
|
||||
"name": "Maximum Scheduled Jobs",
|
||||
"description": "Maximum number of the same job template that can be waiting to run when launching from a schedule before no more are created",
|
||||
"default": SCHEDULE_MAX_JOBS,
|
||||
"type": "int",
|
||||
"category": "jobs",
|
||||
},
|
||||
"STDOUT_MAX_BYTES_DISPLAY": {
|
||||
"name": "Standard Output Maximum Display Size",
|
||||
"description": "Maximum Size of Standard Output in bytes to display before requiring the output be downloaded",
|
||||
"default": STDOUT_MAX_BYTES_DISPLAY,
|
||||
"type": "int",
|
||||
"category": "jobs",
|
||||
},
|
||||
"AUTH_TOKEN_EXPIRATION": {
|
||||
"name": "Idle Time Force Log Out",
|
||||
"description": "Number of seconds that a user is inactive before they will need to login again",
|
||||
"type": "int",
|
||||
"default": AUTH_TOKEN_EXPIRATION,
|
||||
"category": "authentication",
|
||||
},
|
||||
"AUTH_TOKEN_PER_USER": {
|
||||
"name": "Maximum number of simultaneous logins",
|
||||
"description": "Maximum number of simultaneous logins a user may have. To disable enter -1",
|
||||
"type": "int",
|
||||
"default": AUTH_TOKEN_PER_USER,
|
||||
"category": "authentication",
|
||||
},
|
||||
# "AUTH_BASIC_ENABLED": {
|
||||
# "name": "Enable HTTP Basic Auth",
|
||||
# "description": "Enable HTTP Basic Auth for the API Browser",
|
||||
# "default": AUTH_BASIC_ENABLED,
|
||||
# "type": "bool",
|
||||
# "category": "authentication",
|
||||
# },
|
||||
# "AUTH_LDAP_SERVER_URI": {
|
||||
# "name": "LDAP Server URI",
|
||||
# "description": "URI Location of the LDAP Server",
|
||||
# "default": AUTH_LDAP_SERVER_URI,
|
||||
# "type": "string",
|
||||
# "category": "authentication",
|
||||
# },
|
||||
# "RADIUS_SERVER": {
|
||||
# "name": "Radius Server Host",
|
||||
# "description": "Host to communicate with for Radius Authentication",
|
||||
# "default": RADIUS_SERVER,
|
||||
# "type": "string",
|
||||
# "category": "authentication",
|
||||
# },
|
||||
# "RADIUS_PORT": {
|
||||
# "name": "Radius Server Port",
|
||||
# "description": "Port on the Radius host for Radius Authentication",
|
||||
# "default": RADIUS_PORT,
|
||||
# "type": "string",
|
||||
# "category": "authentication",
|
||||
# },
|
||||
# "RADIUS_SECRET": {
|
||||
# "name": "Radius Server Secret",
|
||||
# "description": "Secret used when negotiating with the Radius server",
|
||||
# "default": RADIUS_SECRET,
|
||||
# "type": "string",
|
||||
# "category": "authentication",
|
||||
# },
|
||||
"AWX_PROOT_ENABLED": {
|
||||
"name": "Enable PRoot for Job Execution",
|
||||
"description": "Isolates an Ansible job from protected parts of the Tower system to prevent exposing sensitive information",
|
||||
"default": AWX_PROOT_ENABLED,
|
||||
"type": "bool",
|
||||
"category": "jobs",
|
||||
},
|
||||
"AWX_PROOT_HIDE_PATHS": {
|
||||
"name": "Paths to hide from PRoot jobs",
|
||||
"description": "Extra paths to hide from PRoot isolated processes",
|
||||
"default": AWX_PROOT_HIDE_PATHS,
|
||||
"type": "list",
|
||||
"category": "jobs",
|
||||
},
|
||||
"AWX_PROOT_SHOW_PATHS": {
|
||||
"name": "Paths to expose to PRoot jobs",
|
||||
"description": "Explicit whitelist of paths to expose to PRoot jobs",
|
||||
"default": AWX_PROOT_SHOW_PATHS,
|
||||
"type": "list",
|
||||
"category": "jobs",
|
||||
},
|
||||
"AWX_PROOT_BASE_PATH": {
|
||||
"name": "Base PRoot execution path",
|
||||
"description": "The location that PRoot will create its temporary working directory",
|
||||
"default": AWX_PROOT_BASE_PATH,
|
||||
"type": "string",
|
||||
"category": "jobs",
|
||||
},
|
||||
"AWX_ANSIBLE_CALLBACK_PLUGINS": {
|
||||
"name": "Ansible Callback Plugins",
|
||||
"description": "Colon Seperated Paths for extra callback plugins to be used when running jobs",
|
||||
"default": AWX_ANSIBLE_CALLBACK_PLUGINS,
|
||||
"type": "string",
|
||||
"category": "jobs",
|
||||
},
|
||||
"PENDO_TRACKING_STATE": {
|
||||
"name": "Analytics Tracking State",
|
||||
"description": "Enable or Disable Analytics Tracking",
|
||||
"default": PENDO_TRACKING_STATE,
|
||||
"type": "string",
|
||||
"category": "ui",
|
||||
},
|
||||
"AD_HOC_COMMANDS": {
|
||||
"name": "Ansible Modules Allowed for Ad Hoc Jobs",
|
||||
"description": "A colon-seperated whitelist of modules allowed to be used by ad-hoc jobs",
|
||||
"default": AD_HOC_COMMANDS,
|
||||
"type": "list",
|
||||
"category": "jobs",
|
||||
},
|
||||
"ACTIVITY_STREAM_ENABLED": {
|
||||
"name": "Enable Activity Stream",
|
||||
"description": "Enable capturing activity for the Tower activity stream",
|
||||
"default": ACTIVITY_STREAM_ENABLED,
|
||||
"type": "bool",
|
||||
"category": "system",
|
||||
},
|
||||
"ORG_ADMINS_CAN_SEE_ALL_USERS": {
|
||||
"name": "All Users Visible to Organization Admins",
|
||||
"description": "Controls whether any Organization Admin can view all users, even those not associated with their Organization",
|
||||
"default": ORG_ADMINS_CAN_SEE_ALL_USERS,
|
||||
"type": "bool",
|
||||
"category": "system",
|
||||
},
|
||||
"LICENSE": {
|
||||
"name": "Tower License",
|
||||
"description": "Controls what features and functionality is enabled in Tower.",
|
||||
"default": "{}",
|
||||
"type": "string",
|
||||
"category": "system",
|
||||
},
|
||||
}
|
||||
# Logging configuration.
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
|
Loading…
Reference in New Issue
Block a user