django i18n

This commit is contained in:
sundeep-co-in
2016-10-18 17:56:40 +05:30
parent 5e8a7eeb8a
commit 78a8ce9479
12 changed files with 182 additions and 145 deletions

1
.gitignore vendored
View File

@@ -106,6 +106,7 @@ reports
*.log.[0-9] *.log.[0-9]
*.results *.results
local/ local/
*.mo
# AWX python libs populated by requirements.txt # AWX python libs populated by requirements.txt
awx/lib/.deps_built awx/lib/.deps_built

View File

@@ -493,6 +493,25 @@ test_tox:
# Alias existing make target so old versions run against Jekins the same way # Alias existing make target so old versions run against Jekins the same way
test_jenkins : test_coverage test_jenkins : test_coverage
# l10n TASKS
# --------------------------------------
LANG = "en-us"
messages:
@if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/tower/bin/activate; \
fi; \
$(PYTHON) manage.py makemessages -l $(LANG)
languages:
@if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/tower/bin/activate; \
fi; \
$(PYTHON) manage.py compilemessages
# End l10n TASKS
# --------------------------------------
# UI TASKS # UI TASKS
# -------------------------------------- # --------------------------------------
@@ -506,7 +525,7 @@ ui-docker-machine: $(UI_DEPS_FLAG_FILE)
ui-docker: $(UI_DEPS_FLAG_FILE) ui-docker: $(UI_DEPS_FLAG_FILE)
$(NPM_BIN) --prefix awx/ui run build-docker-cid $(NPM_BIN) --prefix awx/ui run build-docker-cid
ui-release: $(UI_RELEASE_FLAG_FILE) ui-release: languages $(UI_RELEASE_FLAG_FILE)
$(UI_RELEASE_FLAG_FILE): $(UI_DEPS_FLAG_FILE) $(UI_RELEASE_FLAG_FILE): $(UI_DEPS_FLAG_FILE)
$(NPM_BIN) --prefix awx/ui run build-release $(NPM_BIN) --prefix awx/ui run build-release

View File

@@ -7,6 +7,7 @@ from collections import OrderedDict
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import Http404 from django.http import Http404
from django.utils.encoding import force_text, smart_text from django.utils.encoding import force_text, smart_text
from django.utils.translation import ugettext_lazy as _
# Django REST Framework # Django REST Framework
from rest_framework import exceptions from rest_framework import exceptions
@@ -46,15 +47,15 @@ class Metadata(metadata.SimpleMetadata):
serializer = getattr(field, 'parent', None) serializer = getattr(field, 'parent', None)
if serializer: if serializer:
field_help_text = { field_help_text = {
'id': 'Database ID for this {}.', 'id': _('Database ID for this {}.'),
'name': 'Name of this {}.', 'name': _('Name of this {}.'),
'description': 'Optional description of this {}.', 'description': _('Optional description of this {}.'),
'type': 'Data type for this {}.', 'type': _('Data type for this {}.'),
'url': 'URL for this {}.', 'url': _('URL for this {}.'),
'related': 'Data structure with URLs of related resources.', 'related': _('Data structure with URLs of related resources.'),
'summary_fields': 'Data structure with name/description for related resources.', 'summary_fields': _('Data structure with name/description for related resources.'),
'created': 'Timestamp when this {} was created.', 'created': _('Timestamp when this {} was created.'),
'modified': 'Timestamp when this {} was last modified.', 'modified': _('Timestamp when this {} was last modified.'),
} }
if field.field_name in field_help_text: if field.field_name in field_help_text:
if hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'): if hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'):

View File

@@ -20,7 +20,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError
from django.db import models from django.db import models
# from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.text import capfirst from django.utils.text import capfirst
@@ -694,9 +694,9 @@ class UnifiedJobStdoutSerializer(UnifiedJobSerializer):
class UserSerializer(BaseSerializer): class UserSerializer(BaseSerializer):
password = serializers.CharField(required=False, default='', write_only=True, password = serializers.CharField(required=False, default='', write_only=True,
help_text='Write-only field used to change the password.') help_text=_('Write-only field used to change the password.'))
ldap_dn = serializers.CharField(source='profile.ldap_dn', read_only=True) ldap_dn = serializers.CharField(source='profile.ldap_dn', read_only=True)
external_account = serializers.SerializerMethodField(help_text='Set if the account is managed by an external service') external_account = serializers.SerializerMethodField(help_text=_('Set if the account is managed by an external service'))
is_system_auditor = serializers.BooleanField(default=False) is_system_auditor = serializers.BooleanField(default=False)
show_capabilities = ['edit', 'delete'] show_capabilities = ['edit', 'delete']
@@ -961,7 +961,7 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
class ProjectPlaybooksSerializer(ProjectSerializer): class ProjectPlaybooksSerializer(ProjectSerializer):
playbooks = serializers.ReadOnlyField(help_text='Array of playbooks available within this project.') playbooks = serializers.ReadOnlyField(help_text=_('Array of playbooks available within this project.'))
class Meta: class Meta:
model = Project model = Project
@@ -1717,18 +1717,18 @@ class CredentialSerializerCreate(CredentialSerializer):
user = serializers.PrimaryKeyRelatedField( user = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), queryset=User.objects.all(),
required=False, default=None, write_only=True, allow_null=True, required=False, default=None, write_only=True, allow_null=True,
help_text='Write-only field used to add user to owner role. If provided, ' help_text=_('Write-only field used to add user to owner role. If provided, '
'do not give either team or organization. Only valid for creation.') 'do not give either team or organization. Only valid for creation.'))
team = serializers.PrimaryKeyRelatedField( team = serializers.PrimaryKeyRelatedField(
queryset=Team.objects.all(), queryset=Team.objects.all(),
required=False, default=None, write_only=True, allow_null=True, required=False, default=None, write_only=True, allow_null=True,
help_text='Write-only field used to add team to owner role. If provided, ' help_text=_('Write-only field used to add team to owner role. If provided, '
'do not give either user or organization. Only valid for creation.') 'do not give either user or organization. Only valid for creation.'))
organization = serializers.PrimaryKeyRelatedField( organization = serializers.PrimaryKeyRelatedField(
queryset=Organization.objects.all(), queryset=Organization.objects.all(),
required=False, default=None, write_only=True, allow_null=True, required=False, default=None, write_only=True, allow_null=True,
help_text='Write-only field used to add organization to owner role. If provided, ' help_text=_('Write-only field used to add organization to owner role. If provided, '
'do not give either team or team. Only valid for creation.') 'do not give either team or team. Only valid for creation.'))
class Meta: class Meta:
model = Credential model = Credential
@@ -2791,15 +2791,15 @@ class ActivityStreamSerializer(BaseSerializer):
ret = super(ActivityStreamSerializer, self).get_fields() ret = super(ActivityStreamSerializer, self).get_fields()
for key, field in ret.items(): for key, field in ret.items():
if key == 'changes': if key == 'changes':
field.help_text = 'A summary of the new and changed values when an object is created, updated, or deleted' field.help_text = _('A summary of the new and changed values when an object is created, updated, or deleted')
if key == 'object1': if key == 'object1':
field.help_text = ('For create, update, and delete events this is the object type that was affected. ' field.help_text = _('For create, update, and delete events this is the object type that was affected. '
'For associate and disassociate events this is the object type associated or disassociated with object2.') 'For associate and disassociate events this is the object type associated or disassociated with object2.')
if key == 'object2': if key == 'object2':
field.help_text = ('Unpopulated for create, update, and delete events. For associate and disassociate ' field.help_text = _('Unpopulated for create, update, and delete events. For associate and disassociate '
'events this is the object type that object1 is being associated with.') 'events this is the object type that object1 is being associated with.')
if key == 'operation': if key == 'operation':
field.help_text = 'The action taken with respect to the given object(s).' field.help_text = _('The action taken with respect to the given object(s).')
return ret return ret
def get_changes(self, obj): def get_changes(self, obj):

View File

@@ -29,6 +29,7 @@ from django.template.loader import render_to_string
from django.core.servers.basehttp import FileWrapper from django.core.servers.basehttp import FileWrapper
from django.http import HttpResponse from django.http import HttpResponse
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _
# Django REST Framework # Django REST Framework
@@ -86,14 +87,14 @@ class ApiRootView(APIView):
authentication_classes = [] authentication_classes = []
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
view_name = 'REST API' view_name = _('REST API')
def get(self, request, format=None): def get(self, request, format=None):
''' list supported API versions ''' ''' list supported API versions '''
current = reverse('api:api_v1_root_view', args=[]) current = reverse('api:api_v1_root_view', args=[])
data = dict( data = dict(
description = 'Ansible Tower REST API', description = _('Ansible Tower REST API'),
current_version = current, current_version = current,
available_versions = dict( available_versions = dict(
v1 = current v1 = current
@@ -105,7 +106,7 @@ class ApiV1RootView(APIView):
authentication_classes = [] authentication_classes = []
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
view_name = 'Version 1' view_name = _('Version 1')
def get(self, request, format=None): def get(self, request, format=None):
''' list top level resources ''' ''' list top level resources '''
@@ -156,7 +157,7 @@ class ApiV1PingView(APIView):
""" """
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
authentication_classes = () authentication_classes = ()
view_name = 'Ping' view_name = _('Ping')
new_in_210 = True new_in_210 = True
def get(self, request, format=None): def get(self, request, format=None):
@@ -182,7 +183,7 @@ class ApiV1PingView(APIView):
class ApiV1ConfigView(APIView): class ApiV1ConfigView(APIView):
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
view_name = 'Configuration' view_name = _('Configuration')
def get(self, request, format=None): def get(self, request, format=None):
'''Return various sitewide configuration settings.''' '''Return various sitewide configuration settings.'''
@@ -278,7 +279,7 @@ class ApiV1ConfigView(APIView):
class DashboardView(APIView): class DashboardView(APIView):
view_name = "Dashboard" view_name = _("Dashboard")
new_in_14 = True new_in_14 = True
def get(self, request, format=None): def get(self, request, format=None):
@@ -383,7 +384,7 @@ class DashboardView(APIView):
class DashboardJobsGraphView(APIView): class DashboardJobsGraphView(APIView):
view_name = "Dashboard Jobs Graphs" view_name = _("Dashboard Jobs Graphs")
new_in_200 = True new_in_200 = True
def get(self, request, format=None): def get(self, request, format=None):
@@ -433,7 +434,7 @@ class DashboardJobsGraphView(APIView):
class ScheduleList(ListAPIView): class ScheduleList(ListAPIView):
view_name = "Schedules" view_name = _("Schedules")
model = Schedule model = Schedule
serializer_class = ScheduleSerializer serializer_class = ScheduleSerializer
new_in_148 = True new_in_148 = True
@@ -450,7 +451,7 @@ class ScheduleUnifiedJobsList(SubListAPIView):
serializer_class = UnifiedJobSerializer serializer_class = UnifiedJobSerializer
parent_model = Schedule parent_model = Schedule
relationship = 'unifiedjob_set' relationship = 'unifiedjob_set'
view_name = 'Schedule Jobs List' view_name = _('Schedule Jobs List')
new_in_148 = True new_in_148 = True
class AuthView(APIView): class AuthView(APIView):
@@ -998,7 +999,7 @@ class ProjectTeamsList(ListAPIView):
class ProjectSchedulesList(SubListCreateAttachDetachAPIView): class ProjectSchedulesList(SubListCreateAttachDetachAPIView):
view_name = "Project Schedules" view_name = _("Project Schedules")
model = Schedule model = Schedule
serializer_class = ScheduleSerializer serializer_class = ScheduleSerializer
@@ -1162,7 +1163,7 @@ class UserMeList(ListAPIView):
model = User model = User
serializer_class = UserSerializer serializer_class = UserSerializer
view_name = 'Me' view_name = _('Me')
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(pk=self.request.user.pk) return self.model.objects.filter(pk=self.request.user.pk)
@@ -2038,7 +2039,7 @@ class InventoryInventorySourcesList(SubListAPIView):
serializer_class = InventorySourceSerializer serializer_class = InventorySourceSerializer
parent_model = Inventory parent_model = Inventory
relationship = None # Not defined since using get_queryset(). relationship = None # Not defined since using get_queryset().
view_name = 'Inventory Source List' view_name = _('Inventory Source List')
new_in_14 = True new_in_14 = True
def get_queryset(self): def get_queryset(self):
@@ -2071,7 +2072,7 @@ class InventorySourceDetail(RetrieveUpdateAPIView):
class InventorySourceSchedulesList(SubListCreateAttachDetachAPIView): class InventorySourceSchedulesList(SubListCreateAttachDetachAPIView):
view_name = "Inventory Source Schedules" view_name = _("Inventory Source Schedules")
model = Schedule model = Schedule
serializer_class = ScheduleSerializer serializer_class = ScheduleSerializer
@@ -2292,7 +2293,7 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
class JobTemplateSchedulesList(SubListCreateAttachDetachAPIView): class JobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
view_name = "Job Template Schedules" view_name = _("Job Template Schedules")
model = Schedule model = Schedule
serializer_class = ScheduleSerializer serializer_class = ScheduleSerializer
@@ -2823,7 +2824,7 @@ class SystemJobTemplateLaunch(GenericAPIView):
class SystemJobTemplateSchedulesList(SubListCreateAttachDetachAPIView): class SystemJobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
view_name = "System Job Template Schedules" view_name = _("System Job Template Schedules")
model = Schedule model = Schedule
serializer_class = ScheduleSerializer serializer_class = ScheduleSerializer
@@ -2991,7 +2992,7 @@ class BaseJobHostSummariesList(SubListAPIView):
serializer_class = JobHostSummarySerializer serializer_class = JobHostSummarySerializer
parent_model = None # Subclasses must define this attribute. parent_model = None # Subclasses must define this attribute.
relationship = 'job_host_summaries' relationship = 'job_host_summaries'
view_name = 'Job Host Summaries List' view_name = _('Job Host Summaries List')
class HostJobHostSummariesList(BaseJobHostSummariesList): class HostJobHostSummariesList(BaseJobHostSummariesList):
@@ -3026,7 +3027,7 @@ class JobEventChildrenList(SubListAPIView):
serializer_class = JobEventSerializer serializer_class = JobEventSerializer
parent_model = JobEvent parent_model = JobEvent
relationship = 'children' relationship = 'children'
view_name = 'Job Event Children List' view_name = _('Job Event Children List')
class JobEventHostsList(SubListAPIView): class JobEventHostsList(SubListAPIView):
@@ -3034,7 +3035,7 @@ class JobEventHostsList(SubListAPIView):
serializer_class = HostSerializer serializer_class = HostSerializer
parent_model = JobEvent parent_model = JobEvent
relationship = 'hosts' relationship = 'hosts'
view_name = 'Job Event Hosts List' view_name = _('Job Event Hosts List')
class BaseJobEventsList(SubListAPIView): class BaseJobEventsList(SubListAPIView):
@@ -3042,7 +3043,7 @@ class BaseJobEventsList(SubListAPIView):
serializer_class = JobEventSerializer serializer_class = JobEventSerializer
parent_model = None # Subclasses must define this attribute. parent_model = None # Subclasses must define this attribute.
relationship = 'job_events' relationship = 'job_events'
view_name = 'Job Events List' view_name = _('Job Events List')
class HostJobEventsList(BaseJobEventsList): class HostJobEventsList(BaseJobEventsList):
@@ -3074,7 +3075,7 @@ class JobJobEventsList(BaseJobEventsList):
class JobJobPlaysList(BaseJobEventsList): class JobJobPlaysList(BaseJobEventsList):
parent_model = Job parent_model = Job
view_name = 'Job Plays List' view_name = _('Job Plays List')
new_in_200 = True new_in_200 = True
@paginated @paginated
@@ -3149,7 +3150,7 @@ class JobJobTasksList(BaseJobEventsList):
and their completion status. and their completion status.
""" """
parent_model = Job parent_model = Job
view_name = 'Job Play Tasks List' view_name = _('Job Play Tasks List')
new_in_200 = True new_in_200 = True
@paginated @paginated
@@ -3444,7 +3445,7 @@ class BaseAdHocCommandEventsList(SubListAPIView):
serializer_class = AdHocCommandEventSerializer serializer_class = AdHocCommandEventSerializer
parent_model = None # Subclasses must define this attribute. parent_model = None # Subclasses must define this attribute.
relationship = 'ad_hoc_command_events' relationship = 'ad_hoc_command_events'
view_name = 'Ad Hoc Command Events List' view_name = _('Ad Hoc Command Events List')
new_in_220 = True new_in_220 = True
@@ -3660,7 +3661,7 @@ class NotificationTemplateDetail(RetrieveUpdateDestroyAPIView):
class NotificationTemplateTest(GenericAPIView): class NotificationTemplateTest(GenericAPIView):
view_name = 'NotificationTemplate Test' view_name = _('NotificationTemplate Test')
model = NotificationTemplate model = NotificationTemplate
serializer_class = EmptySerializer serializer_class = EmptySerializer
new_in_300 = True new_in_300 = True

View File

@@ -11,6 +11,7 @@ from django.conf import settings
from django.db.models import Q from django.db.models import Q
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _
# Django REST Framework # Django REST Framework
from rest_framework.exceptions import ParseError, PermissionDenied, ValidationError from rest_framework.exceptions import ParseError, PermissionDenied, ValidationError
@@ -199,24 +200,24 @@ class BaseAccess(object):
validation_info['grace_period_remaining'] = 99999999 validation_info['grace_period_remaining'] = 99999999
if check_expiration and validation_info.get('time_remaining', None) is None: if check_expiration and validation_info.get('time_remaining', None) is None:
raise PermissionDenied("License is missing.") raise PermissionDenied(_("License is missing."))
if check_expiration and validation_info.get("grace_period_remaining") <= 0: if check_expiration and validation_info.get("grace_period_remaining") <= 0:
raise PermissionDenied("License has expired.") raise PermissionDenied(_("License has expired."))
free_instances = validation_info.get('free_instances', 0) free_instances = validation_info.get('free_instances', 0)
available_instances = validation_info.get('available_instances', 0) available_instances = validation_info.get('available_instances', 0)
if add_host and free_instances == 0: if add_host and free_instances == 0:
raise PermissionDenied("License count of %s instances has been reached." % available_instances) raise PermissionDenied(_("License count of %s instances has been reached.") % available_instances)
elif add_host and free_instances < 0: elif add_host and free_instances < 0:
raise PermissionDenied("License count of %s instances has been exceeded." % available_instances) raise PermissionDenied(_("License count of %s instances has been exceeded.") % available_instances)
elif not add_host and free_instances < 0: elif not add_host and free_instances < 0:
raise PermissionDenied("Host count exceeds available instances.") raise PermissionDenied(_("Host count exceeds available instances."))
if feature is not None: if feature is not None:
if "features" in validation_info and not validation_info["features"].get(feature, False): if "features" in validation_info and not validation_info["features"].get(feature, False):
raise LicenseForbids("Feature %s is not enabled in the active license." % feature) raise LicenseForbids(_("Feature %s is not enabled in the active license.") % feature)
elif "features" not in validation_info: elif "features" not in validation_info:
raise LicenseForbids("Features not found in active license.") raise LicenseForbids(_("Features not found in active license."))
def get_user_capabilities(self, obj, method_list=[], parent_obj=None): def get_user_capabilities(self, obj, method_list=[], parent_obj=None):
if obj is None: if obj is None:
@@ -535,7 +536,7 @@ class HostAccess(BaseAccess):
# Prevent moving a host to a different inventory. # Prevent moving a host to a different inventory.
inventory_pk = get_pk_from_dict(data, 'inventory') inventory_pk = get_pk_from_dict(data, 'inventory')
if obj and inventory_pk and obj.inventory.pk != inventory_pk: if obj and inventory_pk and obj.inventory.pk != inventory_pk:
raise PermissionDenied('Unable to change inventory on a host.') raise PermissionDenied(_('Unable to change inventory on a host.'))
# Checks for admin or change permission on inventory, controls whether # Checks for admin or change permission on inventory, controls whether
# the user can edit variable data. # the user can edit variable data.
return obj and self.user in obj.inventory.admin_role return obj and self.user in obj.inventory.admin_role
@@ -547,7 +548,7 @@ class HostAccess(BaseAccess):
return False return False
# Prevent assignments between different inventories. # Prevent assignments between different inventories.
if obj.inventory != sub_obj.inventory: if obj.inventory != sub_obj.inventory:
raise ParseError('Cannot associate two items from different inventories.') raise ParseError(_('Cannot associate two items from different inventories.'))
return True return True
def can_delete(self, obj): def can_delete(self, obj):
@@ -581,7 +582,7 @@ class GroupAccess(BaseAccess):
# Prevent moving a group to a different inventory. # Prevent moving a group to a different inventory.
inventory_pk = get_pk_from_dict(data, 'inventory') inventory_pk = get_pk_from_dict(data, 'inventory')
if obj and inventory_pk and obj.inventory.pk != inventory_pk: if obj and inventory_pk and obj.inventory.pk != inventory_pk:
raise PermissionDenied('Unable to change inventory on a group.') raise PermissionDenied(_('Unable to change inventory on a group.'))
# Checks for admin or change permission on inventory, controls whether # Checks for admin or change permission on inventory, controls whether
# the user can attach subgroups or edit variable data. # the user can attach subgroups or edit variable data.
return obj and self.user in obj.inventory.admin_role return obj and self.user in obj.inventory.admin_role
@@ -593,7 +594,7 @@ class GroupAccess(BaseAccess):
return False return False
# Prevent assignments between different inventories. # Prevent assignments between different inventories.
if obj.inventory != sub_obj.inventory: if obj.inventory != sub_obj.inventory:
raise ParseError('Cannot associate two items from different inventories.') raise ParseError(_('Cannot associate two items from different inventories.'))
# Prevent group from being assigned as its own (grand)child. # Prevent group from being assigned as its own (grand)child.
if type(obj) == type(sub_obj): if type(obj) == type(sub_obj):
parent_pks = set(obj.all_parents.values_list('pk', flat=True)) parent_pks = set(obj.all_parents.values_list('pk', flat=True))
@@ -805,7 +806,7 @@ class TeamAccess(BaseAccess):
# Prevent moving a team to a different organization. # Prevent moving a team to a different organization.
org_pk = get_pk_from_dict(data, 'organization') org_pk = get_pk_from_dict(data, 'organization')
if obj and org_pk and obj.organization.pk != org_pk: if obj and org_pk and obj.organization.pk != org_pk:
raise PermissionDenied('Unable to change organization on a team.') raise PermissionDenied(_('Unable to change organization on a team.'))
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return self.user in obj.admin_role return self.user in obj.admin_role
@@ -818,9 +819,9 @@ class TeamAccess(BaseAccess):
of a resource role to the team.""" of a resource role to the team."""
if isinstance(sub_obj, Role): if isinstance(sub_obj, Role):
if sub_obj.content_object is None: if sub_obj.content_object is None:
raise PermissionDenied("The {} role cannot be assigned to a team".format(sub_obj.name)) raise PermissionDenied(_("The {} role cannot be assigned to a team").format(sub_obj.name))
elif isinstance(sub_obj.content_object, User): elif isinstance(sub_obj.content_object, User):
raise PermissionDenied("The admin_role for a User cannot be assigned to a team") raise PermissionDenied(_("The admin_role for a User cannot be assigned to a team"))
if isinstance(sub_obj.content_object, ResourceMixin): if isinstance(sub_obj.content_object, ResourceMixin):
role_access = RoleAccess(self.user) role_access = RoleAccess(self.user)

View File

@@ -4,6 +4,7 @@
# Django # Django
from django.shortcuts import render from django.shortcuts import render
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
# Django REST Framework # Django REST Framework
from rest_framework import exceptions, permissions, views from rest_framework import exceptions, permissions, views
@@ -16,7 +17,7 @@ class ApiErrorView(views.APIView):
metadata_class = None metadata_class = None
allowed_methods = ('GET', 'HEAD') allowed_methods = ('GET', 'HEAD')
exception_class = exceptions.APIException exception_class = exceptions.APIException
view_name = 'API Error' view_name = _('API Error')
def get_view_name(self): def get_view_name(self):
return self.view_name return self.view_name
@@ -45,31 +46,31 @@ def handle_error(request, status=404, **kwargs):
def handle_400(request): def handle_400(request):
kwargs = { kwargs = {
'name': 'Bad Request', 'name': _('Bad Request'),
'content': 'The request could not be understood by the server.', 'content': _('The request could not be understood by the server.'),
} }
return handle_error(request, 400, **kwargs) return handle_error(request, 400, **kwargs)
def handle_403(request): def handle_403(request):
kwargs = { kwargs = {
'name': 'Forbidden', 'name': _('Forbidden'),
'content': 'You don\'t have permission to access the requested resource.', 'content': _('You don\'t have permission to access the requested resource.'),
} }
return handle_error(request, 403, **kwargs) return handle_error(request, 403, **kwargs)
def handle_404(request): def handle_404(request):
kwargs = { kwargs = {
'name': 'Not Found', 'name': _('Not Found'),
'content': 'The requested resource could not be found.', 'content': _('The requested resource could not be found.'),
} }
return handle_error(request, 404, **kwargs) return handle_error(request, 404, **kwargs)
def handle_500(request): def handle_500(request):
kwargs = { kwargs = {
'name': 'Server Error', 'name': _('Server Error'),
'content': 'A server error has occurred.', 'content': _('A server error has occurred.'),
} }
return handle_error(request, 500, **kwargs) return handle_error(request, 500, **kwargs)

View File

@@ -18,6 +18,9 @@ for setting in dir(global_settings):
if setting == setting.upper(): if setting == setting.upper():
setattr(this_module, setting, getattr(global_settings, setting)) setattr(this_module, setting, getattr(global_settings, setting))
# gettext
from django.utils.translation import ugettext_lazy as _
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@@ -118,6 +121,11 @@ LOG_ROOT = os.path.join(BASE_DIR)
# The heartbeat file for the tower scheduler # The heartbeat file for the tower scheduler
SCHEDULE_METADATA_LOCATION = os.path.join(BASE_DIR, '.tower_cycle') SCHEDULE_METADATA_LOCATION = os.path.join(BASE_DIR, '.tower_cycle')
# Django gettext files path: locale/<lang-code>/LC_MESSAGES/django.po, django.mo
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
# Maximum number of the same job that can be waiting to run when launching from scheduler # Maximum number of the same job that can be waiting to run when launching from scheduler
# Note: This setting may be overridden by database settings. # Note: This setting may be overridden by database settings.
SCHEDULE_MAX_JOBS = 10 SCHEDULE_MAX_JOBS = 10
@@ -155,8 +163,9 @@ TEMPLATE_CONTEXT_PROCESSORS = ( # NOQA
) )
MIDDLEWARE_CLASSES = ( # NOQA MIDDLEWARE_CLASSES = ( # NOQA
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
@@ -560,12 +569,12 @@ AD_HOC_COMMANDS = [
# instead (based on docs from: # instead (based on docs from:
# http://docs.rackspace.com/loadbalancers/api/v1.0/clb-devguide/content/Service_Access_Endpoints-d1e517.html) # http://docs.rackspace.com/loadbalancers/api/v1.0/clb-devguide/content/Service_Access_Endpoints-d1e517.html)
RAX_REGION_CHOICES = [ RAX_REGION_CHOICES = [
('ORD', 'Chicago'), ('ORD', _('Chicago')),
('DFW', 'Dallas/Ft. Worth'), ('DFW', _('Dallas/Ft. Worth')),
('IAD', 'Northern Virginia'), ('IAD', _('Northern Virginia')),
('LON', 'London'), ('LON', _('London')),
('SYD', 'Sydney'), ('SYD', _('Sydney')),
('HKG', 'Hong Kong'), ('HKG', _('Hong Kong')),
] ]
# Inventory variable name/values for determining if host is active/enabled. # Inventory variable name/values for determining if host is active/enabled.
@@ -592,18 +601,18 @@ INV_ENV_VARIABLE_BLACKLIST = ("HOME", "USER", "_", "TERM")
# list of names here. The available region IDs will be pulled from boto. # list of names here. The available region IDs will be pulled from boto.
# http://docs.aws.amazon.com/general/latest/gr/rande.html#ec2_region # http://docs.aws.amazon.com/general/latest/gr/rande.html#ec2_region
EC2_REGION_NAMES = { EC2_REGION_NAMES = {
'us-east-1': 'US East (Northern Virginia)', 'us-east-1': _('US East (Northern Virginia)'),
'us-west-2': 'US West (Oregon)', 'us-west-2': _('US West (Oregon)'),
'us-west-1': 'US West (Northern California)', 'us-west-1': _('US West (Northern California)'),
'eu-central-1': 'EU (Frankfurt)', 'eu-central-1': _('EU (Frankfurt)'),
'eu-west-1': 'EU (Ireland)', 'eu-west-1': _('EU (Ireland)'),
'ap-southeast-1': 'Asia Pacific (Singapore)', 'ap-southeast-1': _('Asia Pacific (Singapore)'),
'ap-southeast-2': 'Asia Pacific (Sydney)', 'ap-southeast-2': _('Asia Pacific (Sydney)'),
'ap-northeast-1': 'Asia Pacific (Tokyo)', 'ap-northeast-1': _('Asia Pacific (Tokyo)'),
'ap-northeast-2': 'Asia Pacific (Seoul)', 'ap-northeast-2': _('Asia Pacific (Seoul)'),
'sa-east-1': 'South America (Sao Paulo)', 'sa-east-1': _('South America (Sao Paulo)'),
'us-gov-west-1': 'US West (GovCloud)', 'us-gov-west-1': _('US West (GovCloud)'),
'cn-north-1': 'China (Beijing)', 'cn-north-1': _('China (Beijing)'),
} }
EC2_REGIONS_BLACKLIST = [ EC2_REGIONS_BLACKLIST = [
@@ -652,19 +661,19 @@ VMWARE_EXCLUDE_EMPTY_GROUPS = True
# provide a list here. # provide a list here.
# Source: https://developers.google.com/compute/docs/zones # Source: https://developers.google.com/compute/docs/zones
GCE_REGION_CHOICES = [ GCE_REGION_CHOICES = [
('us-east1-b', 'US East (B)'), ('us-east1-b', _('US East (B)')),
('us-east1-c', 'US East (C)'), ('us-east1-c', _('US East (C)')),
('us-east1-d', 'US East (D)'), ('us-east1-d', _('US East (D)')),
('us-central1-a', 'US Central (A)'), ('us-central1-a', _('US Central (A)')),
('us-central1-b', 'US Central (B)'), ('us-central1-b', _('US Central (B)')),
('us-central1-c', 'US Central (C)'), ('us-central1-c', _('US Central (C)')),
('us-central1-f', 'US Central (F)'), ('us-central1-f', _('US Central (F)')),
('europe-west1-b', 'Europe West (B)'), ('europe-west1-b', _('Europe West (B)')),
('europe-west1-c', 'Europe West (C)'), ('europe-west1-c', _('Europe West (C)')),
('europe-west1-d', 'Europe West (D)'), ('europe-west1-d', _('Europe West (D)')),
('asia-east1-a', 'Asia East (A)'), ('asia-east1-a', _('Asia East (A)')),
('asia-east1-b', 'Asia East (B)'), ('asia-east1-b', _('Asia East (B)')),
('asia-east1-c', 'Asia East (C)'), ('asia-east1-c', _('Asia East (C)')),
] ]
GCE_REGIONS_BLACKLIST = [] GCE_REGIONS_BLACKLIST = []
@@ -688,19 +697,19 @@ GCE_INSTANCE_ID_VAR = None
# It's not possible to get zones in Azure without authenticating, so we # It's not possible to get zones in Azure without authenticating, so we
# provide a list here. # provide a list here.
AZURE_REGION_CHOICES = [ AZURE_REGION_CHOICES = [
('Central_US', 'US Central'), ('Central_US', _('US Central')),
('East_US_1', 'US East'), ('East_US_1', _('US East')),
('East_US_2', 'US East 2'), ('East_US_2', _('US East 2')),
('North_Central_US', 'US North Central'), ('North_Central_US', _('US North Central')),
('South_Central_US', 'US South Central'), ('South_Central_US', _('US South Central')),
('West_US', 'US West'), ('West_US', _('US West')),
('North_Europe', 'Europe North'), ('North_Europe', _('Europe North')),
('West_Europe', 'Europe West'), ('West_Europe', _('Europe West')),
('East_Asia_Pacific', 'Asia Pacific East'), ('East_Asia_Pacific', _('Asia Pacific East')),
('Southest_Asia_Pacific', 'Asia Pacific Southeast'), ('Southest_Asia_Pacific', _('Asia Pacific Southeast')),
('East_Japan', 'Japan East'), ('East_Japan', _('Japan East')),
('West_Japan', 'Japan West'), ('West_Japan', _('Japan West')),
('South_Brazil', 'Brazil South'), ('South_Brazil', _('Brazil South')),
] ]
AZURE_REGIONS_BLACKLIST = [] AZURE_REGIONS_BLACKLIST = []

View File

@@ -7,6 +7,9 @@ import re
# Python Social Auth # Python Social Auth
from social.exceptions import AuthException from social.exceptions import AuthException
# Django
from django.utils.translation import ugettext_lazy as _
# Tower # Tower
from awx.conf.license import feature_enabled from awx.conf.license import feature_enabled
@@ -18,13 +21,13 @@ class AuthNotFound(AuthException):
super(AuthNotFound, self).__init__(backend, *args, **kwargs) super(AuthNotFound, self).__init__(backend, *args, **kwargs)
def __str__(self): def __str__(self):
return 'An account cannot be found for {0}'.format(self.email_or_uid) return _('An account cannot be found for {0}').format(self.email_or_uid)
class AuthInactive(AuthException): class AuthInactive(AuthException):
def __str__(self): def __str__(self):
return 'Your account is inactive' return _('Your account is inactive')
def check_user_found_or_created(backend, details, user=None, *args, **kwargs): def check_user_found_or_created(backend, details, user=None, *args, **kwargs):

View File

@@ -36,9 +36,9 @@
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li><a href="{% url 'api:user_me_list' %}" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Logged in as {{ user }}{% if user.get_full_name %} ({{ user.get_full_name }}){% endif %}"><span class="glyphicon glyphicon-user"></span> <span class="visible-xs-inline">Logged in as </span>{{ user }}{% if user.get_full_name %}<span class="visible-xs-inline"> ({{ user.get_full_name }})</span>{% endif %}</a></li> <li><a href="{% url 'api:user_me_list' %}" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Logged in as {{ user }}{% if user.get_full_name %} ({{ user.get_full_name }}){% endif %}"><span class="glyphicon glyphicon-user"></span> <span class="visible-xs-inline">Logged in as </span>{{ user }}{% if user.get_full_name %}<span class="visible-xs-inline"> ({{ user.get_full_name }})</span>{% endif %}</a></li>
{% endif %} {% endif %}
<li><a href="//docs.ansible.com/ansible-tower/{{short_tower_version}}/html/towerapi/index.html" target="_blank" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Ansible Tower API Guide"><span class="glyphicon glyphicon-question-sign"></span><span class="visible-xs-inline"> Ansible Tower API Guide</span></a></li> <li><a href="//docs.ansible.com/ansible-tower/{{short_tower_version}}/html/towerapi/index.html" target="_blank" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Ansible Tower API Guide' %}"><span class="glyphicon glyphicon-question-sign"></span><span class="visible-xs-inline">{% trans 'Ansible Tower API Guide' %}</span></a></li>
<li><a href="/" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Back to Ansible Tower"><span class="glyphicon glyphicon-circle-arrow-left"></span><span class="visible-xs-inline"> Back to Ansible Tower</span></a></li> <li><a href="/" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Back to Ansible Tower' %}"><span class="glyphicon glyphicon-circle-arrow-left"></span><span class="visible-xs-inline">{% trans 'Back to Ansible Tower' %}</span></a></li>
<li class="hidden-xs"><a href="#" class="resize" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Resize"><span class="glyphicon glyphicon-resize-full"></span></a></li> <li class="hidden-xs"><a href="#" class="resize" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Resize' %}"><span class="glyphicon glyphicon-resize-full"></span></a></li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@@ -1,8 +1,8 @@
<!DOCTYPE html>
{# Copy of base.html from rest_framework with minor Ansible Tower change. #} {# Copy of base.html from rest_framework with minor Ansible Tower change. #}
{% load staticfiles %} {% load staticfiles %}
{% load rest_framework %} {% load rest_framework %}
{% load i18n %} {% load i18n %}
<!DOCTYPE html>
<html> <html>
<head> <head>
{% block head %} {% block head %}
@@ -75,21 +75,21 @@
<fieldset> <fieldset>
{% if api_settings.URL_FORMAT_OVERRIDE %} {% if api_settings.URL_FORMAT_OVERRIDE %}
<div class="btn-group format-selection"> <div class="btn-group format-selection">
<a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a> <a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="{% blocktrans %}Make a GET request on the {{ name }} resource{% endblocktrans %}">GET</a>
<button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request"> <button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="{% trans 'Specify a format for the GET request' %}">
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for format in available_formats %} {% for format in available_formats %}
<li> <li>
<a class="js-tooltip format-option" href="{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}" rel="nofollow" title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a> <a class="js-tooltip format-option" href="{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}" rel="nofollow" title="{% blocktrans %}Make a GET request on the {{ name }} resource with the format set to `{{ format }}`{% endblocktrans %}">{{ format }}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
{% else %} {% else %}
<a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a> <a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="{% blocktrans %}Make a GET request on the {{ name }} resource{% endblocktrans %}">GET</a>
{% endif %} {% endif %}
</fieldset> </fieldset>
</form> </form>
@@ -97,13 +97,13 @@
{% if options_form %} {% if options_form %}
<form class="button-form" action="{{ request.get_full_path }}" data-method="OPTIONS"> <form class="button-form" action="{{ request.get_full_path }}" data-method="OPTIONS">
<button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button> <button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make an OPTIONS request on the {{ name }} resource{% endblocktrans %}">OPTIONS</button>
</form> </form>
{% endif %} {% endif %}
{% if delete_form %} {% if delete_form %}
<form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE"> <form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE">
<button class="btn btn-danger js-tooltip" title="Make a DELETE request on the {{ name }} resource">DELETE</button> <button class="btn btn-danger js-tooltip" title="{% blocktrans %}Make a DELETE request on the {{ name }} resource{% endblocktrans %}">DELETE</button>
</form> </form>
{% endif %} {% endif %}
@@ -169,7 +169,7 @@
{% csrf_token %} {% csrf_token %}
{{ post_form }} {{ post_form }}
<div class="form-actions"> <div class="form-actions">
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button> <button class="btn btn-primary" title="{% blocktrans %}Make a POST request on the {{ name }} resource{% endblocktrans %}">POST</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>
@@ -183,7 +183,7 @@
<fieldset> <fieldset>
{% include "rest_framework/raw_data_form.html" %} {% include "rest_framework/raw_data_form.html" %}
<div class="form-actions"> <div class="form-actions">
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button> <button class="btn btn-primary" title="{% blocktrans %}Make a POST request on the {{ name }} resource{% endblocktrans %}">POST</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>
@@ -213,7 +213,7 @@
<fieldset> <fieldset>
{{ put_form }} {{ put_form }}
<div class="form-actions"> <div class="form-actions">
<button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button> <button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PUT request on the {{ name }} resource{% endblocktrans %}">PUT</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>
@@ -227,10 +227,10 @@
{% include "rest_framework/raw_data_form.html" %} {% include "rest_framework/raw_data_form.html" %}
<div class="form-actions"> <div class="form-actions">
{% if raw_data_put_form %} {% if raw_data_put_form %}
<button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button> <button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PUT request on the {{ name }} resource{% endblocktrans %}">PUT</button>
{% endif %} {% endif %}
{% if raw_data_patch_form %} {% if raw_data_patch_form %}
<button data-method="PATCH" class="btn btn-primary js-tooltip" title="Make a PATCH request on the {{ name }} resource">PATCH</button> <button data-method="PATCH" class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PATCH request on the {{ name }} resource{% endblocktrans %}">PATCH</button>
{% endif %} {% endif %}
</div> </div>
</fieldset> </fieldset>

View File

@@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
{% load i18n %}
<html lang="en" ng-app="Tower"> <html lang="en" ng-app="Tower">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Ansible Tower</title> <title>{% trans 'Ansible Tower' %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="{{ STATIC_URL }}assets/custom-theme/jquery-ui-1.10.3.custom.min.css" /> <link rel="stylesheet" href="{{ STATIC_URL }}assets/custom-theme/jquery-ui-1.10.3.custom.min.css" />
<link rel="stylesheet" href="{{ STATIC_URL }}assets/ansible-bootstrap.min.css" /> <link rel="stylesheet" href="{{ STATIC_URL }}assets/ansible-bootstrap.min.css" />
@@ -49,7 +50,7 @@
<!-- Password Dialog --> <!-- Password Dialog -->
<div id="password-modal" style="display: none;"></div> <div id="password-modal" style="display: none;"></div>
<div id="idle-modal" style="display:none">Your session will expire in <span id="remaining_seconds" class="IdleModal-remainingSeconds">60</span> seconds, would you like to continue?</div> <div id="idle-modal" style="display:none">{% blocktrans %}Your session will expire in <span id="remaining_seconds" class="IdleModal-remainingSeconds">60</span> seconds, would you like to continue?{% endblocktrans %}</div>
<stream-detail-modal></stream-detail-modal> <stream-detail-modal></stream-detail-modal>
@@ -155,27 +156,27 @@
<div id="prompt-for-days" style="display:none"> <div id="prompt-for-days" style="display:none">
<form name="prompt_for_days_form" id="prompt_for_days_form" class="MgmtCards-promptText"> <form name="prompt_for_days_form" id="prompt_for_days_form" class="MgmtCards-promptText">
Set how many days of data should be retained. <br> {% trans 'Set how many days of data should be retained.' %}<br>
<input type="integer" id="days_to_keep" name="days_to_keep" ng-model="days_to_keep" ng-required="true" class="form-control Form-textInput" min=0 max=9999 style="margin-top:10px;" integer> <input type="integer" id="days_to_keep" name="days_to_keep" ng-model="days_to_keep" ng-required="true" class="form-control Form-textInput" min=0 max=9999 style="margin-top:10px;" integer>
<div class="error" ng-show="prompt_for_days_form.days_to_keep.$dirty && (prompt_for_days_form.days_to_keep.$error.number || prompt_for_days_form.days_to_keep.$error.integer || <div class="error" ng-show="prompt_for_days_form.days_to_keep.$dirty && (prompt_for_days_form.days_to_keep.$error.number || prompt_for_days_form.days_to_keep.$error.integer ||
prompt_for_days_form.days_to_keep.$error.required || prompt_for_days_form.days_to_keep.$error.required ||
prompt_for_days_form.days_to_keep.$error.min || prompt_for_days_form.days_to_keep.$error.min ||
prompt_for_days_form.days_to_keep.$error.max)">Please enter an integer<span ng-show="prompt_for_days_form.days_to_keep.$dirty && prompt_for_days_form.days_to_keep.$error.min"> that is not negative</span><span ng-show="prompt_for_days_form.days_to_keep.$dirty && prompt_for_days_form.days_to_keep.$error.max"> that is lower than 9999</span>.</div> prompt_for_days_form.days_to_keep.$error.max)">{% blocktrans %}Please enter an integer<span ng-show="prompt_for_days_form.days_to_keep.$dirty && prompt_for_days_form.days_to_keep.$error.min"> that is not negative</span><span ng-show="prompt_for_days_form.days_to_keep.$dirty && prompt_for_days_form.days_to_keep.$error.max"> that is lower than 9999</span>.{% endblocktrans %}</div>
</form> </form>
</div> </div>
<div id="prompt-for-days-facts" style="display:none"> <div id="prompt-for-days-facts" style="display:none">
<form name="prompt_for_days_facts_form" id="prompt_for_days_facts_form" class="MgmtCards-promptText"> <form name="prompt_for_days_facts_form" id="prompt_for_days_facts_form" class="MgmtCards-promptText">
<div style="padding-bottom:15px;">For facts collected older than the time period specified, <div style="padding-bottom:15px;">{% blocktrans %}For facts collected older than the time period specified,
save one fact scan (snapshot) per time window (frequency). save one fact scan (snapshot) per time window (frequency).
For example, facts older than 30 days are purged, while one For example, facts older than 30 days are purged, while one
weekly fact scan is kept.<br> <br> weekly fact scan is kept.<br> <br>
CAUTION: Setting both numerical variables to "0" will delete all facts.<br><br> CAUTION: Setting both numerical variables to "0" will delete all facts.<br><br>{% endblocktrans %}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description"> <label for="description">
<span class="label-text"> <span class="label-text">
Select a time period after which to remove old facts {% trans 'Select a time period after which to remove old facts' %}
</span> </span>
</label> </label>
<div class="row"> <div class="row">
@@ -189,12 +190,12 @@
<div class="error" ng-show="prompt_for_days_facts_form.keep_amount.$dirty && (prompt_for_days_facts_form.keep_amount.$error.number || prompt_for_days_facts_form.keep_amount.$error.integer || <div class="error" ng-show="prompt_for_days_facts_form.keep_amount.$dirty && (prompt_for_days_facts_form.keep_amount.$error.number || prompt_for_days_facts_form.keep_amount.$error.integer ||
prompt_for_days_facts_form.keep_amount.$error.required || prompt_for_days_facts_form.keep_amount.$error.required ||
prompt_for_days_facts_form.keep_amount.$error.min || prompt_for_days_facts_form.keep_amount.$error.min ||
prompt_for_days_facts_form.keep_amount.$error.max)">Please enter an integer<span ng-show="prompt_for_days_facts_form.keep_amount.$dirty && prompt_for_days_facts_form.keep_amount.$error.min"> that is not negative</span><span ng-show="prompt_for_days_facts_form.keep_amount.$dirty && prompt_for_days_facts_form.keep_amount.$error.max"> that is lower than 9999</span>.</div> prompt_for_days_facts_form.keep_amount.$error.max)">{% blocktrans %}Please enter an integer<span ng-show="prompt_for_days_facts_form.keep_amount.$dirty && prompt_for_days_facts_form.keep_amount.$error.min"> that is not negative</span><span ng-show="prompt_for_days_facts_form.keep_amount.$dirty && prompt_for_days_facts_form.keep_amount.$error.max"> that is lower than 9999</span>.{% endblocktrans %}</div>
</div> </div>
<div class="form-group "> <div class="form-group ">
<label for="description"> <label for="description">
<span class="label-text"> <span class="label-text">
Select a frequency for snapshot retention {% trans 'Select a frequency for snapshot retention' %}
</span> </span>
</label> </label>
<div class="row"> <div class="row">
@@ -209,14 +210,14 @@
<div class="error" ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && (prompt_for_days_facts_form.granularity_keep_amount.$error.number || prompt_for_days_facts_form.granularity_keep_amount.$error.integer || <div class="error" ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && (prompt_for_days_facts_form.granularity_keep_amount.$error.number || prompt_for_days_facts_form.granularity_keep_amount.$error.integer ||
prompt_for_days_facts_form.granularity_keep_amount.$error.required || prompt_for_days_facts_form.granularity_keep_amount.$error.required ||
prompt_for_days_facts_form.granularity_keep_amount.$error.min || prompt_for_days_facts_form.granularity_keep_amount.$error.min ||
prompt_for_days_facts_form.granularity_keep_amount.$error.max)">Please enter an integer<span ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && prompt_for_days_facts_form.granularity_keep_amount.$error.min"> that is not negative</span><span ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && prompt_for_days_facts_form.granularity_keep_amount.$error.max"> that is lower than 9999</span>.</div> prompt_for_days_facts_form.granularity_keep_amount.$error.max)">{% blocktrans %}Please enter an integer<span ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && prompt_for_days_facts_form.granularity_keep_amount.$error.min"> that is not negative</span><span ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && prompt_for_days_facts_form.granularity_keep_amount.$error.max"> that is lower than 9999</span>.{% endblocktrans %}</div>
</div> </div>
</form> </form>
</div> </div>
<div class="overlay"></div> <div class="overlay"></div>
<div class="spinny"><i class="fa fa-cog fa-spin fa-2x"></i> <p>working...</p></div> <div class="spinny"><i class="fa fa-cog fa-spin fa-2x"></i> <p>{% trans 'working...' %}</p></div>
</div> </div>
<tower-footer></tower-footer> <tower-footer></tower-footer>
</body> </body>