mirror of
https://github.com/ansible/awx.git
synced 2026-05-15 21:37:42 -02:30
Fix migration issues, tests, and templates
This commit is contained in:
@@ -22,6 +22,7 @@ from rest_framework.filters import BaseFilterBackend
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.utils import get_type_for_model, to_python_boolean
|
from awx.main.utils import get_type_for_model, to_python_boolean
|
||||||
|
from awx.main.utils.db import get_all_field_names
|
||||||
from awx.main.models.credential import CredentialType
|
from awx.main.models.credential import CredentialType
|
||||||
from awx.main.models.rbac import RoleAncestorEntry
|
from awx.main.models.rbac import RoleAncestorEntry
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@ class TypeFilterBackend(BaseFilterBackend):
|
|||||||
types_map[ct_type] = ct.pk
|
types_map[ct_type] = ct.pk
|
||||||
model = queryset.model
|
model = queryset.model
|
||||||
model_type = get_type_for_model(model)
|
model_type = get_type_for_model(model)
|
||||||
if 'polymorphic_ctype' in model._meta.get_all_field_names():
|
if 'polymorphic_ctype' in get_all_field_names(model):
|
||||||
types_pks = set([v for k,v in types_map.items() if k in types])
|
types_pks = set([v for k,v in types_map.items() if k in types])
|
||||||
queryset = queryset.filter(polymorphic_ctype_id__in=types_pks)
|
queryset = queryset.filter(polymorphic_ctype_id__in=types_pks)
|
||||||
elif model_type in types:
|
elif model_type in types:
|
||||||
@@ -119,7 +120,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
|||||||
'last_updated': 'last_job_run',
|
'last_updated': 'last_job_run',
|
||||||
}.get(name, name)
|
}.get(name, name)
|
||||||
|
|
||||||
if name == 'type' and 'polymorphic_ctype' in model._meta.get_all_field_names():
|
if name == 'type' and 'polymorphic_ctype' in get_all_field_names(model):
|
||||||
name = 'polymorphic_ctype'
|
name = 'polymorphic_ctype'
|
||||||
new_parts.append('polymorphic_ctype__model')
|
new_parts.append('polymorphic_ctype__model')
|
||||||
else:
|
else:
|
||||||
@@ -136,7 +137,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
|||||||
new_parts.pop()
|
new_parts.pop()
|
||||||
new_parts.append(name_alt)
|
new_parts.append(name_alt)
|
||||||
else:
|
else:
|
||||||
field = model._meta.get_field_by_name(name)[0]
|
field = model._meta.get_field(name)
|
||||||
if isinstance(field, ForeignObjectRel) and getattr(field.field, '__prevent_search__', False):
|
if isinstance(field, ForeignObjectRel) and getattr(field.field, '__prevent_search__', False):
|
||||||
raise PermissionDenied(_('Filtering on %s is not allowed.' % name))
|
raise PermissionDenied(_('Filtering on %s is not allowed.' % name))
|
||||||
elif getattr(field, '__prevent_search__', False):
|
elif getattr(field, '__prevent_search__', False):
|
||||||
@@ -375,7 +376,7 @@ class OrderByBackend(BaseFilterBackend):
|
|||||||
# given the limited number of views with multiple types,
|
# given the limited number of views with multiple types,
|
||||||
# sorting on polymorphic_ctype.model is effectively the same.
|
# sorting on polymorphic_ctype.model is effectively the same.
|
||||||
new_order_by = []
|
new_order_by = []
|
||||||
if 'polymorphic_ctype' in queryset.model._meta.get_all_field_names():
|
if 'polymorphic_ctype' in get_all_field_names(queryset.model):
|
||||||
for field in order_by:
|
for field in order_by:
|
||||||
if field == 'type':
|
if field == 'type':
|
||||||
new_order_by.append('polymorphic_ctype__model')
|
new_order_by.append('polymorphic_ctype__model')
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from rest_framework import views
|
|||||||
from awx.api.filters import FieldLookupBackend
|
from awx.api.filters import FieldLookupBackend
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
from awx.main.utils import * # noqa
|
from awx.main.utils import * # noqa
|
||||||
|
from awx.main.utils.db import get_all_field_names
|
||||||
from awx.api.serializers import ResourceAccessListElementSerializer
|
from awx.api.serializers import ResourceAccessListElementSerializer
|
||||||
from awx.api.versioning import URLPathVersioning, get_request_version
|
from awx.api.versioning import URLPathVersioning, get_request_version
|
||||||
from awx.api.metadata import SublistAttachDetatchMetadata
|
from awx.api.metadata import SublistAttachDetatchMetadata
|
||||||
@@ -321,8 +322,7 @@ class ListAPIView(generics.ListAPIView, GenericAPIView):
|
|||||||
return page
|
return page
|
||||||
|
|
||||||
def get_description_context(self):
|
def get_description_context(self):
|
||||||
opts = self.model._meta
|
if 'username' in get_all_field_names(self.model):
|
||||||
if 'username' in opts.get_all_field_names():
|
|
||||||
order_field = 'username'
|
order_field = 'username'
|
||||||
else:
|
else:
|
||||||
order_field = 'name'
|
order_field = 'name'
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||||||
return super(BaseSerializer, self).run_validation(data)
|
return super(BaseSerializer, self).run_validation(data)
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
# Avoid bug? in DRF if exc.detail happens to be a list instead of a dict.
|
# Avoid bug? in DRF if exc.detail happens to be a list instead of a dict.
|
||||||
raise ValidationError(detail=serializers.get_validation_error_detail(exc))
|
raise ValidationError(detail=serializers.as_serializer_error(exc))
|
||||||
|
|
||||||
def get_validation_exclusions(self, obj=None):
|
def get_validation_exclusions(self, obj=None):
|
||||||
# Borrowed from DRF 2.x - return model fields that should be excluded
|
# Borrowed from DRF 2.x - return model fields that should be excluded
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ from django.utils.timezone import now
|
|||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
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 _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@@ -53,7 +52,9 @@ import qsstats
|
|||||||
import ansiconv
|
import ansiconv
|
||||||
|
|
||||||
# Python Social Auth
|
# Python Social Auth
|
||||||
from social.backends.utils import load_backends
|
from social_core.backends.utils import load_backends
|
||||||
|
|
||||||
|
from wsgiref.util import FileWrapper
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.tasks import send_notifications
|
from awx.main.tasks import send_notifications
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ def get_object_from_data(field, Model, data, obj=None):
|
|||||||
# Calling method needs to deal with non-existence of key
|
# Calling method needs to deal with non-existence of key
|
||||||
raise ParseError(_("Required related field %s for permission check." % field))
|
raise ParseError(_("Required related field %s for permission check." % field))
|
||||||
|
|
||||||
if isinstance(raw_value, Model):
|
try:
|
||||||
return raw_value
|
if isinstance(raw_value, Model):
|
||||||
elif raw_value is None:
|
return raw_value
|
||||||
return None
|
elif raw_value is None:
|
||||||
else:
|
return None
|
||||||
try:
|
else:
|
||||||
new_pk = int(raw_value)
|
new_pk = int(raw_value)
|
||||||
# Avoid database query by comparing pk to model for similarity
|
# Avoid database query by comparing pk to model for similarity
|
||||||
if obj and new_pk == getattr(obj, '%s_id' % field, None):
|
if obj and new_pk == getattr(obj, '%s_id' % field, None):
|
||||||
@@ -67,8 +67,8 @@ def get_object_from_data(field, Model, data, obj=None):
|
|||||||
else:
|
else:
|
||||||
# Get the new resource from the database
|
# Get the new resource from the database
|
||||||
return get_object_or_400(Model, pk=new_pk)
|
return get_object_or_400(Model, pk=new_pk)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
raise ParseError(_("Bad data found in related field %s." % field))
|
raise ParseError(_("Bad data found in related field %s." % field))
|
||||||
|
|
||||||
|
|
||||||
class StateConflict(ValidationError):
|
class StateConflict(ValidationError):
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ from django.db.models.signals import (
|
|||||||
)
|
)
|
||||||
from django.db.models.signals import m2m_changed
|
from django.db.models.signals import m2m_changed
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.fields.related import (
|
from django.db.models.fields.related import add_lazy_relation
|
||||||
add_lazy_relation,
|
from django.db.models.fields.related_descriptors import (
|
||||||
SingleRelatedObjectDescriptor,
|
ReverseOneToOneDescriptor,
|
||||||
ReverseSingleRelatedObjectDescriptor,
|
ForwardManyToOneDescriptor,
|
||||||
ManyRelatedObjectsDescriptor,
|
ManyToManyDescriptor,
|
||||||
ReverseManyRelatedObjectsDescriptor,
|
ReverseManyToOneDescriptor,
|
||||||
)
|
)
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@@ -96,7 +96,7 @@ class JSONBField(upstream_JSONBField):
|
|||||||
# https://bitbucket.org/offline/django-annoying/src/a0de8b294db3/annoying/fields.py
|
# https://bitbucket.org/offline/django-annoying/src/a0de8b294db3/annoying/fields.py
|
||||||
|
|
||||||
|
|
||||||
class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
|
class AutoSingleRelatedObjectDescriptor(ReverseOneToOneDescriptor):
|
||||||
"""Descriptor for access to the object from its related class."""
|
"""Descriptor for access to the object from its related class."""
|
||||||
|
|
||||||
def __get__(self, instance, instance_type=None):
|
def __get__(self, instance, instance_type=None):
|
||||||
@@ -139,7 +139,7 @@ def resolve_role_field(obj, field):
|
|||||||
raise Exception(smart_text('{} refers to a {}, not a Role'.format(field, type(obj))))
|
raise Exception(smart_text('{} refers to a {}, not a Role'.format(field, type(obj))))
|
||||||
ret.append(obj.id)
|
ret.append(obj.id)
|
||||||
else:
|
else:
|
||||||
if type(obj) is ManyRelatedObjectsDescriptor:
|
if type(obj) is ManyToManyDescriptor:
|
||||||
for o in obj.all():
|
for o in obj.all():
|
||||||
ret += resolve_role_field(o, field_components[1])
|
ret += resolve_role_field(o, field_components[1])
|
||||||
else:
|
else:
|
||||||
@@ -179,7 +179,7 @@ def is_implicit_parent(parent_role, child_role):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
|
class ImplicitRoleDescriptor(ForwardManyToOneDescriptor):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -230,18 +230,18 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
field_name, sep, field_attr = field_name.partition('.')
|
field_name, sep, field_attr = field_name.partition('.')
|
||||||
field = getattr(cls, field_name)
|
field = getattr(cls, field_name)
|
||||||
|
|
||||||
if type(field) is ReverseManyRelatedObjectsDescriptor or \
|
if type(field) is ReverseManyToOneDescriptor or \
|
||||||
type(field) is ManyRelatedObjectsDescriptor:
|
type(field) is ManyToManyDescriptor:
|
||||||
|
|
||||||
if '.' in field_attr:
|
if '.' in field_attr:
|
||||||
raise Exception('Referencing deep roles through ManyToMany fields is unsupported.')
|
raise Exception('Referencing deep roles through ManyToMany fields is unsupported.')
|
||||||
|
|
||||||
if type(field) is ReverseManyRelatedObjectsDescriptor:
|
if type(field) is ReverseManyToOneDescriptor:
|
||||||
sender = field.through
|
sender = field.through
|
||||||
else:
|
else:
|
||||||
sender = field.related.through
|
sender = field.related.through
|
||||||
|
|
||||||
reverse = type(field) is ManyRelatedObjectsDescriptor
|
reverse = type(field) is ManyToManyDescriptor
|
||||||
m2m_changed.connect(self.m2m_update(field_attr, reverse), sender, weak=False)
|
m2m_changed.connect(self.m2m_update(field_attr, reverse), sender, weak=False)
|
||||||
|
|
||||||
def m2m_update(self, field_attr, _reverse):
|
def m2m_update(self, field_attr, _reverse):
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class BaseTestMixin(MockCommonlySlowTestMixin):
|
|||||||
# Set flag so that task chain works with unit tests.
|
# Set flag so that task chain works with unit tests.
|
||||||
settings.CELERY_UNIT_TEST = True
|
settings.CELERY_UNIT_TEST = True
|
||||||
settings.SYSTEM_UUID='00000000-0000-0000-0000-000000000000'
|
settings.SYSTEM_UUID='00000000-0000-0000-0000-000000000000'
|
||||||
settings.BROKER_URL='redis://localhost:55672/'
|
settings.CELERY_BROKER_URL='redis://localhost:55672/'
|
||||||
settings.CALLBACK_QUEUE = 'callback_tasks_unit'
|
settings.CALLBACK_QUEUE = 'callback_tasks_unit'
|
||||||
|
|
||||||
# Disable socket notifications for unit tests.
|
# Disable socket notifications for unit tests.
|
||||||
|
|||||||
@@ -92,24 +92,21 @@ class TestJobTemplateCopyEdit:
|
|||||||
credential=None, ask_credential_on_launch=True,
|
credential=None, ask_credential_on_launch=True,
|
||||||
name='deploy-job-template'
|
name='deploy-job-template'
|
||||||
)
|
)
|
||||||
serializer = JobTemplateSerializer(jt_res)
|
serializer = JobTemplateSerializer(jt_res, context=self.fake_context(admin_user))
|
||||||
serializer.context = self.fake_context(admin_user)
|
|
||||||
response = serializer.to_representation(jt_res)
|
response = serializer.to_representation(jt_res)
|
||||||
assert not response['summary_fields']['user_capabilities']['copy']
|
assert not response['summary_fields']['user_capabilities']['copy']
|
||||||
assert response['summary_fields']['user_capabilities']['edit']
|
assert response['summary_fields']['user_capabilities']['edit']
|
||||||
|
|
||||||
def test_sys_admin_copy_edit(self, jt_copy_edit, admin_user):
|
def test_sys_admin_copy_edit(self, jt_copy_edit, admin_user):
|
||||||
"Absent a validation error, system admins can do everything"
|
"Absent a validation error, system admins can do everything"
|
||||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(admin_user))
|
||||||
serializer.context = self.fake_context(admin_user)
|
|
||||||
response = serializer.to_representation(jt_copy_edit)
|
response = serializer.to_representation(jt_copy_edit)
|
||||||
assert response['summary_fields']['user_capabilities']['copy']
|
assert response['summary_fields']['user_capabilities']['copy']
|
||||||
assert response['summary_fields']['user_capabilities']['edit']
|
assert response['summary_fields']['user_capabilities']['edit']
|
||||||
|
|
||||||
def test_org_admin_copy_edit(self, jt_copy_edit, org_admin):
|
def test_org_admin_copy_edit(self, jt_copy_edit, org_admin):
|
||||||
"Organization admins SHOULD be able to copy a JT firmly in their org"
|
"Organization admins SHOULD be able to copy a JT firmly in their org"
|
||||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(org_admin))
|
||||||
serializer.context = self.fake_context(org_admin)
|
|
||||||
response = serializer.to_representation(jt_copy_edit)
|
response = serializer.to_representation(jt_copy_edit)
|
||||||
assert response['summary_fields']['user_capabilities']['copy']
|
assert response['summary_fields']['user_capabilities']['copy']
|
||||||
assert response['summary_fields']['user_capabilities']['edit']
|
assert response['summary_fields']['user_capabilities']['edit']
|
||||||
@@ -125,8 +122,7 @@ class TestJobTemplateCopyEdit:
|
|||||||
jt_copy_edit.credential = machine_credential
|
jt_copy_edit.credential = machine_credential
|
||||||
jt_copy_edit.save()
|
jt_copy_edit.save()
|
||||||
|
|
||||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(org_admin))
|
||||||
serializer.context = self.fake_context(org_admin)
|
|
||||||
response = serializer.to_representation(jt_copy_edit)
|
response = serializer.to_representation(jt_copy_edit)
|
||||||
assert not response['summary_fields']['user_capabilities']['copy']
|
assert not response['summary_fields']['user_capabilities']['copy']
|
||||||
assert response['summary_fields']['user_capabilities']['edit']
|
assert response['summary_fields']['user_capabilities']['edit']
|
||||||
@@ -140,8 +136,7 @@ class TestJobTemplateCopyEdit:
|
|||||||
jt_copy_edit.admin_role.members.add(rando)
|
jt_copy_edit.admin_role.members.add(rando)
|
||||||
jt_copy_edit.save()
|
jt_copy_edit.save()
|
||||||
|
|
||||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(rando))
|
||||||
serializer.context = self.fake_context(rando)
|
|
||||||
response = serializer.to_representation(jt_copy_edit)
|
response = serializer.to_representation(jt_copy_edit)
|
||||||
assert not response['summary_fields']['user_capabilities']['copy']
|
assert not response['summary_fields']['user_capabilities']['copy']
|
||||||
assert response['summary_fields']['user_capabilities']['edit']
|
assert response['summary_fields']['user_capabilities']['edit']
|
||||||
@@ -155,8 +150,7 @@ class TestJobTemplateCopyEdit:
|
|||||||
jt_copy_edit.project.admin_role.members.add(rando)
|
jt_copy_edit.project.admin_role.members.add(rando)
|
||||||
jt_copy_edit.project.save()
|
jt_copy_edit.project.save()
|
||||||
|
|
||||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(rando))
|
||||||
serializer.context = self.fake_context(rando)
|
|
||||||
response = serializer.to_representation(jt_copy_edit)
|
response = serializer.to_representation(jt_copy_edit)
|
||||||
assert response['summary_fields']['user_capabilities']['copy']
|
assert response['summary_fields']['user_capabilities']['copy']
|
||||||
assert response['summary_fields']['user_capabilities']['edit']
|
assert response['summary_fields']['user_capabilities']['edit']
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ def run_command(name, *args, **options):
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"username,password,expected,changed", [
|
"username,password,expected,changed", [
|
||||||
('admin', 'dingleberry', 'Password updated\n', True),
|
('admin', 'dingleberry', 'Password updated', True),
|
||||||
('admin', 'admin', 'Password not updated\n', False),
|
('admin', 'admin', 'Password not updated', False),
|
||||||
(None, 'foo', 'username required', False),
|
(None, 'foo', 'username required', False),
|
||||||
('admin', None, 'password required', False),
|
('admin', None, 'password required', False),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class TestInvalidOptionsFunctional:
|
|||||||
cmd = inventory_import.Command()
|
cmd = inventory_import.Command()
|
||||||
with mock.patch('django.db.transaction.rollback'):
|
with mock.patch('django.db.transaction.rollback'):
|
||||||
with pytest.raises(IOError) as err:
|
with pytest.raises(IOError) as err:
|
||||||
cmd.handle_noargs(
|
cmd.handle(
|
||||||
inventory_id=inventory.id,
|
inventory_id=inventory.id,
|
||||||
source='/tmp/pytest-of-root/pytest-7/inv_files0-invalid')
|
source='/tmp/pytest-of-root/pytest-7/inv_files0-invalid')
|
||||||
assert 'Source does not exist' in err.value.message
|
assert 'Source does not exist' in err.value.message
|
||||||
@@ -100,14 +100,14 @@ class TestInvalidOptionsFunctional:
|
|||||||
def test_invalid_inventory_id(self):
|
def test_invalid_inventory_id(self):
|
||||||
cmd = inventory_import.Command()
|
cmd = inventory_import.Command()
|
||||||
with pytest.raises(CommandError) as err:
|
with pytest.raises(CommandError) as err:
|
||||||
cmd.handle_noargs(inventory_id=42, source='/notapath/shouldnotmatter')
|
cmd.handle(inventory_id=42, source='/notapath/shouldnotmatter')
|
||||||
assert 'id = 42' in err.value.message
|
assert 'id = 42' in err.value.message
|
||||||
assert 'cannot be found' in err.value.message
|
assert 'cannot be found' in err.value.message
|
||||||
|
|
||||||
def test_invalid_inventory_name(self):
|
def test_invalid_inventory_name(self):
|
||||||
cmd = inventory_import.Command()
|
cmd = inventory_import.Command()
|
||||||
with pytest.raises(CommandError) as err:
|
with pytest.raises(CommandError) as err:
|
||||||
cmd.handle_noargs(inventory_name='fooservers', source='/notapath/shouldnotmatter')
|
cmd.handle(inventory_name='fooservers', source='/notapath/shouldnotmatter')
|
||||||
assert 'name = fooservers' in err.value.message
|
assert 'name = fooservers' in err.value.message
|
||||||
assert 'cannot be found' in err.value.message
|
assert 'cannot be found' in err.value.message
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ class TestINIImports:
|
|||||||
@mock.patch.object(inventory_import.AnsibleInventoryLoader, 'load', mock.MagicMock(return_value=TEST_MEM_OBJECTS))
|
@mock.patch.object(inventory_import.AnsibleInventoryLoader, 'load', mock.MagicMock(return_value=TEST_MEM_OBJECTS))
|
||||||
def test_inventory_single_ini_import(self, inventory, capsys):
|
def test_inventory_single_ini_import(self, inventory, capsys):
|
||||||
cmd = inventory_import.Command()
|
cmd = inventory_import.Command()
|
||||||
r = cmd.handle_noargs(
|
r = cmd.handle(
|
||||||
inventory_id=inventory.pk, source=__file__,
|
inventory_id=inventory.pk, source=__file__,
|
||||||
method='backport')
|
method='backport')
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
@@ -192,7 +192,7 @@ class TestINIImports:
|
|||||||
)
|
)
|
||||||
def test_hostvars_are_saved(self, inventory):
|
def test_hostvars_are_saved(self, inventory):
|
||||||
cmd = inventory_import.Command()
|
cmd = inventory_import.Command()
|
||||||
cmd.handle_noargs(inventory_id=inventory.pk, source='doesnt matter')
|
cmd.handle(inventory_id=inventory.pk, source='doesnt matter')
|
||||||
assert inventory.hosts.count() == 1
|
assert inventory.hosts.count() == 1
|
||||||
h = inventory.hosts.all()[0]
|
h = inventory.hosts.all()[0]
|
||||||
assert h.name == 'foo'
|
assert h.name == 'foo'
|
||||||
@@ -219,4 +219,4 @@ class TestINIImports:
|
|||||||
)
|
)
|
||||||
def test_recursive_group_error(self, inventory):
|
def test_recursive_group_error(self, inventory):
|
||||||
cmd = inventory_import.Command()
|
cmd = inventory_import.Command()
|
||||||
cmd.handle_noargs(inventory_id=inventory.pk, source='doesnt matter')
|
cmd.handle(inventory_id=inventory.pk, source='doesnt matter')
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ def celery_memory_broker():
|
|||||||
|
|
||||||
Allows django signal code to execute without the need for redis
|
Allows django signal code to execute without the need for redis
|
||||||
'''
|
'''
|
||||||
settings.BROKER_URL='memory://localhost/'
|
settings.CELERY_BROKER_URL='memory://localhost/'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def test_get_roles_list_user(organization, inventory, team, get, user):
|
|||||||
'Users can see all roles they have access to, but not all roles'
|
'Users can see all roles they have access to, but not all roles'
|
||||||
this_user = user('user-test_get_roles_list_user')
|
this_user = user('user-test_get_roles_list_user')
|
||||||
organization.member_role.members.add(this_user)
|
organization.member_role.members.add(this_user)
|
||||||
custom_role = Role.objects.create(name='custom_role-test_get_roles_list_user')
|
custom_role = Role.objects.create(role_field='custom_role-test_get_roles_list_user')
|
||||||
organization.member_role.children.add(custom_role)
|
organization.member_role.children.add(custom_role)
|
||||||
|
|
||||||
url = reverse('api:role_list')
|
url = reverse('api:role_list')
|
||||||
@@ -128,7 +128,7 @@ def test_user_view_other_user_roles(organization, inventory, team, get, alice, b
|
|||||||
organization.member_role.members.add(alice)
|
organization.member_role.members.add(alice)
|
||||||
organization.admin_role.members.add(bob)
|
organization.admin_role.members.add(bob)
|
||||||
organization.member_role.members.add(bob)
|
organization.member_role.members.add(bob)
|
||||||
custom_role = Role.objects.create(name='custom_role-test_user_view_admin_roles_list')
|
custom_role = Role.objects.create(role_field='custom_role-test_user_view_admin_roles_list')
|
||||||
organization.member_role.children.add(custom_role)
|
organization.member_role.children.add(custom_role)
|
||||||
team.member_role.members.add(bob)
|
team.member_role.members.add(bob)
|
||||||
|
|
||||||
|
|||||||
@@ -125,11 +125,15 @@ class TestWorkflowJobTemplateNodeSerializerCharPrompts():
|
|||||||
serializer = WorkflowJobTemplateNodeSerializer()
|
serializer = WorkflowJobTemplateNodeSerializer()
|
||||||
node = WorkflowJobTemplateNode(pk=1)
|
node = WorkflowJobTemplateNode(pk=1)
|
||||||
node.char_prompts = {'limit': 'webservers'}
|
node.char_prompts = {'limit': 'webservers'}
|
||||||
serializer.instance = node
|
|
||||||
view = FakeView(node)
|
view = FakeView(node)
|
||||||
view.request = FakeRequest()
|
view.request = FakeRequest()
|
||||||
view.request.method = "PATCH"
|
view.request.method = "PATCH"
|
||||||
serializer.context = {'view': view}
|
|
||||||
|
serializer = WorkflowJobTemplateNodeSerializer()
|
||||||
|
serializer = WorkflowJobTemplateNodeSerializer(context={'view':view})
|
||||||
|
serializer.instance = node
|
||||||
|
|
||||||
return serializer
|
return serializer
|
||||||
|
|
||||||
def test_change_single_field(self, WFJT_serializer):
|
def test_change_single_field(self, WFJT_serializer):
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from awx.api.views import (
|
|||||||
JobTemplateLabelList,
|
JobTemplateLabelList,
|
||||||
JobTemplateSurveySpec,
|
JobTemplateSurveySpec,
|
||||||
InventoryInventorySourcesUpdate,
|
InventoryInventorySourcesUpdate,
|
||||||
InventoryHostsList,
|
|
||||||
HostInsights,
|
HostInsights,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,8 +16,6 @@ from awx.main.models import (
|
|||||||
Host,
|
Host,
|
||||||
)
|
)
|
||||||
|
|
||||||
from awx.main.managers import HostManager
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_response_new(mocker):
|
def mock_response_new(mocker):
|
||||||
@@ -223,17 +220,3 @@ class TestHostInsights():
|
|||||||
|
|
||||||
assert resp.data['error'] == 'The Insights Credential for "inventory_name_here" was not found.'
|
assert resp.data['error'] == 'The Insights Credential for "inventory_name_here" was not found.'
|
||||||
assert resp.status_code == 404
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
class TestInventoryHostsList(object):
|
|
||||||
|
|
||||||
def test_host_list_smart_inventory(self, mocker):
|
|
||||||
Inventory = namedtuple('Inventory', ['kind', 'host_filter', 'hosts', 'organization_id'])
|
|
||||||
obj = Inventory(kind='smart', host_filter='localhost', hosts=HostManager(), organization_id=None)
|
|
||||||
obj.hosts.instance = obj
|
|
||||||
|
|
||||||
with mock.patch.object(InventoryHostsList, 'get_parent_object', return_value=obj):
|
|
||||||
with mock.patch('awx.main.utils.filters.SmartFilter.query_from_string') as mock_query:
|
|
||||||
view = InventoryHostsList()
|
|
||||||
view.get_queryset()
|
|
||||||
mock_query.assert_called_once_with('localhost')
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class TestInvalidOptions:
|
|||||||
def test_invalid_options_no_options_specified(self):
|
def test_invalid_options_no_options_specified(self):
|
||||||
cmd = Command()
|
cmd = Command()
|
||||||
with pytest.raises(CommandError) as err:
|
with pytest.raises(CommandError) as err:
|
||||||
cmd.handle_noargs()
|
cmd.handle()
|
||||||
assert 'inventory-id' in err.value.message
|
assert 'inventory-id' in err.value.message
|
||||||
assert 'required' in err.value.message
|
assert 'required' in err.value.message
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class TestInvalidOptions:
|
|||||||
# You can not specify both name and if of the inventory
|
# You can not specify both name and if of the inventory
|
||||||
cmd = Command()
|
cmd = Command()
|
||||||
with pytest.raises(CommandError) as err:
|
with pytest.raises(CommandError) as err:
|
||||||
cmd.handle_noargs(
|
cmd.handle(
|
||||||
inventory_id=42, inventory_name='my-inventory'
|
inventory_id=42, inventory_name='my-inventory'
|
||||||
)
|
)
|
||||||
assert 'inventory-id' in err.value.message
|
assert 'inventory-id' in err.value.message
|
||||||
@@ -37,7 +37,7 @@ class TestInvalidOptions:
|
|||||||
# You can't overwrite and keep_vars at the same time, that wouldn't make sense
|
# You can't overwrite and keep_vars at the same time, that wouldn't make sense
|
||||||
cmd = Command()
|
cmd = Command()
|
||||||
with pytest.raises(CommandError) as err:
|
with pytest.raises(CommandError) as err:
|
||||||
cmd.handle_noargs(
|
cmd.handle(
|
||||||
inventory_id=42, overwrite=True, keep_vars=True
|
inventory_id=42, overwrite=True, keep_vars=True
|
||||||
)
|
)
|
||||||
assert 'overwrite-vars' in err.value.message
|
assert 'overwrite-vars' in err.value.message
|
||||||
@@ -47,13 +47,13 @@ class TestInvalidOptions:
|
|||||||
# Need a source to import
|
# Need a source to import
|
||||||
cmd = Command()
|
cmd = Command()
|
||||||
with pytest.raises(CommandError) as err:
|
with pytest.raises(CommandError) as err:
|
||||||
cmd.handle_noargs(
|
cmd.handle(
|
||||||
inventory_id=42, overwrite=True, keep_vars=True
|
inventory_id=42, overwrite=True, keep_vars=True
|
||||||
)
|
)
|
||||||
assert 'overwrite-vars' in err.value.message
|
assert 'overwrite-vars' in err.value.message
|
||||||
assert 'exclusive' in err.value.message
|
assert 'exclusive' in err.value.message
|
||||||
with pytest.raises(CommandError) as err:
|
with pytest.raises(CommandError) as err:
|
||||||
cmd.handle_noargs(
|
cmd.handle(
|
||||||
inventory_id=42, overwrite_vars=True, keep_vars=True
|
inventory_id=42, overwrite_vars=True, keep_vars=True
|
||||||
)
|
)
|
||||||
assert 'overwrite-vars' in err.value.message
|
assert 'overwrite-vars' in err.value.message
|
||||||
@@ -62,7 +62,7 @@ class TestInvalidOptions:
|
|||||||
def test_invalid_options_missing_source(self):
|
def test_invalid_options_missing_source(self):
|
||||||
cmd = Command()
|
cmd = Command()
|
||||||
with pytest.raises(CommandError) as err:
|
with pytest.raises(CommandError) as err:
|
||||||
cmd.handle_noargs(inventory_id=42)
|
cmd.handle(inventory_id=42)
|
||||||
assert '--source' in err.value.message
|
assert '--source' in err.value.message
|
||||||
assert 'required' in err.value.message
|
assert 'required' in err.value.message
|
||||||
|
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
import pytest
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
from awx.main.models.rbac import (
|
|
||||||
Role,
|
|
||||||
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
|
||||||
ROLE_SINGLETON_SYSTEM_AUDITOR
|
|
||||||
)
|
|
||||||
from awx.main.models import Organization, JobTemplate, Project
|
|
||||||
|
|
||||||
from awx.main.fields import (
|
|
||||||
ImplicitRoleField,
|
|
||||||
is_implicit_parent
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def apply_fake_roles(obj):
|
|
||||||
'''
|
|
||||||
Creates an un-saved role for all the implicit role fields on an object
|
|
||||||
'''
|
|
||||||
for fd in obj._meta.fields:
|
|
||||||
if not isinstance(fd, ImplicitRoleField):
|
|
||||||
continue
|
|
||||||
r = Role(role_field=fd.name)
|
|
||||||
setattr(obj, fd.name, r)
|
|
||||||
with mock.patch('django.contrib.contenttypes.fields.GenericForeignKey.get_content_type') as mck_ct:
|
|
||||||
mck_ct.return_value = ContentType(model=obj._meta.model_name)
|
|
||||||
r.content_object = obj
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def system_administrator():
|
|
||||||
return Role(
|
|
||||||
role_field=ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
|
||||||
singleton_name=ROLE_SINGLETON_SYSTEM_ADMINISTRATOR
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def system_auditor():
|
|
||||||
return Role(
|
|
||||||
role_field=ROLE_SINGLETON_SYSTEM_AUDITOR,
|
|
||||||
singleton_name=ROLE_SINGLETON_SYSTEM_AUDITOR
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def organization():
|
|
||||||
o = Organization(name='unit-test-org')
|
|
||||||
apply_fake_roles(o)
|
|
||||||
return o
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def project(organization):
|
|
||||||
p = Project(name='unit-test-proj', organization=organization)
|
|
||||||
apply_fake_roles(p)
|
|
||||||
return p
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def job_template(project):
|
|
||||||
jt = JobTemplate(name='unit-test-jt', project=project)
|
|
||||||
apply_fake_roles(jt)
|
|
||||||
return jt
|
|
||||||
|
|
||||||
|
|
||||||
class TestIsImplicitParent:
|
|
||||||
'''
|
|
||||||
Tests to confirm that `is_implicit_parent` gives the right answers
|
|
||||||
'''
|
|
||||||
def test_sys_admin_implicit_parent(self, organization, system_administrator):
|
|
||||||
assert is_implicit_parent(
|
|
||||||
parent_role=system_administrator,
|
|
||||||
child_role=organization.admin_role
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_is_parent_of_member_role(self, organization):
|
|
||||||
assert is_implicit_parent(
|
|
||||||
parent_role=organization.admin_role,
|
|
||||||
child_role=organization.member_role
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_member_is_not_parent_of_admin_role(self, organization):
|
|
||||||
assert not is_implicit_parent(
|
|
||||||
parent_role=organization.member_role,
|
|
||||||
child_role=organization.admin_role
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_second_level_implicit_parent_role(self, job_template, organization):
|
|
||||||
assert is_implicit_parent(
|
|
||||||
parent_role=organization.admin_role,
|
|
||||||
child_role=job_template.admin_role
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_second_level_is_not_an_implicit_parent_role(self, job_template, organization):
|
|
||||||
assert not is_implicit_parent(
|
|
||||||
parent_role=organization.member_role,
|
|
||||||
child_role=job_template.admin_role
|
|
||||||
)
|
|
||||||
@@ -8,11 +8,11 @@ from datetime import timedelta
|
|||||||
('admin_checks', 'awx.main.tasks.run_administrative_checks'),
|
('admin_checks', 'awx.main.tasks.run_administrative_checks'),
|
||||||
('tower_scheduler', 'awx.main.tasks.awx_periodic_scheduler'),
|
('tower_scheduler', 'awx.main.tasks.awx_periodic_scheduler'),
|
||||||
])
|
])
|
||||||
def test_CELERYBEAT_SCHEDULE(mocker, job_name, function_path):
|
def test_CELERY_BEAT_SCHEDULE(mocker, job_name, function_path):
|
||||||
assert job_name in settings.CELERYBEAT_SCHEDULE
|
assert job_name in settings.CELERY_BEAT_SCHEDULE
|
||||||
assert 'schedule' in settings.CELERYBEAT_SCHEDULE[job_name]
|
assert 'schedule' in settings.CELERY_BEAT_SCHEDULE[job_name]
|
||||||
assert type(settings.CELERYBEAT_SCHEDULE[job_name]['schedule']) is timedelta
|
assert type(settings.CELERY_BEAT_SCHEDULE[job_name]['schedule']) is timedelta
|
||||||
assert settings.CELERYBEAT_SCHEDULE[job_name]['task'] == function_path
|
assert settings.CELERY_BEAT_SCHEDULE[job_name]['task'] == function_path
|
||||||
|
|
||||||
# Ensures that the function exists
|
# Ensures that the function exists
|
||||||
mocker.patch(function_path)
|
mocker.patch(function_path)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from awx.main.access import (
|
|||||||
from awx.conf.license import LicenseForbids
|
from awx.conf.license import LicenseForbids
|
||||||
from awx.main.models import (
|
from awx.main.models import (
|
||||||
Credential,
|
Credential,
|
||||||
|
CredentialType,
|
||||||
Inventory,
|
Inventory,
|
||||||
Project,
|
Project,
|
||||||
Role,
|
Role,
|
||||||
@@ -57,7 +58,7 @@ class TestRelatedFieldAccess:
|
|||||||
def test_new_with_bad_data(self, access, mocker):
|
def test_new_with_bad_data(self, access, mocker):
|
||||||
data = {'related': 3.1415}
|
data = {'related': 3.1415}
|
||||||
with pytest.raises(ParseError):
|
with pytest.raises(ParseError):
|
||||||
access.check_related('related', mocker.MagicMock, data)
|
access.check_related('related', mocker.MagicMock(), data)
|
||||||
|
|
||||||
def test_new_mandatory_fail(self, access, mocker):
|
def test_new_mandatory_fail(self, access, mocker):
|
||||||
access.user.is_superuser = False
|
access.user.is_superuser = False
|
||||||
@@ -118,10 +119,18 @@ class TestRelatedFieldAccess:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_with_ids(job_template_factory):
|
def job_template_with_ids(job_template_factory):
|
||||||
# Create non-persisted objects with IDs to send to job_template_factory
|
# Create non-persisted objects with IDs to send to job_template_factory
|
||||||
credential = Credential(id=1, pk=1, name='testcred', kind='ssh')
|
ssh_type = CredentialType(kind='ssh')
|
||||||
net_cred = Credential(id=2, pk=2, name='testnetcred', kind='net')
|
credential = Credential(id=1, pk=1, name='testcred', credential_type=ssh_type)
|
||||||
cloud_cred = Credential(id=3, pk=3, name='testcloudcred', kind='aws')
|
|
||||||
vault_cred = Credential(id=4, pk=4, name='testnetcred', kind='vault')
|
net_type = CredentialType(kind='net')
|
||||||
|
net_cred = Credential(id=2, pk=2, name='testnetcred', credential_type=net_type)
|
||||||
|
|
||||||
|
cloud_type = CredentialType(kind='aws')
|
||||||
|
cloud_cred = Credential(id=3, pk=3, name='testcloudcred', credential_type=cloud_type)
|
||||||
|
|
||||||
|
vault_type = CredentialType(kind='vault')
|
||||||
|
vault_cred = Credential(id=4, pk=4, name='testnetcred', credential_type=vault_type)
|
||||||
|
|
||||||
inv = Inventory(id=11, pk=11, name='testinv')
|
inv = Inventory(id=11, pk=11, name='testinv')
|
||||||
proj = Project(id=14, pk=14, name='testproj')
|
proj = Project(id=14, pk=14, name='testproj')
|
||||||
|
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ def copy_model_by_class(obj1, Class2, fields, kwargs):
|
|||||||
elif not isinstance(Class2._meta.get_field(field_name), (ForeignObjectRel, ManyToManyField)):
|
elif not isinstance(Class2._meta.get_field(field_name), (ForeignObjectRel, ManyToManyField)):
|
||||||
create_kwargs[field_name] = kwargs[field_name]
|
create_kwargs[field_name] = kwargs[field_name]
|
||||||
elif hasattr(obj1, field_name):
|
elif hasattr(obj1, field_name):
|
||||||
field_obj = obj1._meta.get_field_by_name(field_name)[0]
|
field_obj = obj1._meta.get_field(field_name)
|
||||||
if not isinstance(field_obj, ManyToManyField):
|
if not isinstance(field_obj, ManyToManyField):
|
||||||
create_kwargs[field_name] = getattr(obj1, field_name)
|
create_kwargs[field_name] = getattr(obj1, field_name)
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ def copy_m2m_relationships(obj1, obj2, fields, kwargs=None):
|
|||||||
'''
|
'''
|
||||||
for field_name in fields:
|
for field_name in fields:
|
||||||
if hasattr(obj1, field_name):
|
if hasattr(obj1, field_name):
|
||||||
field_obj = obj1._meta.get_field_by_name(field_name)[0]
|
field_obj = obj1._meta.get_field(field_name)
|
||||||
if isinstance(field_obj, ManyToManyField):
|
if isinstance(field_obj, ManyToManyField):
|
||||||
# Many to Many can be specified as field_name
|
# Many to Many can be specified as field_name
|
||||||
src_field_value = getattr(obj1, field_name)
|
src_field_value = getattr(obj1, field_name)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from django.db.migrations.loader import MigrationLoader
|
|||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
|
from itertools import chain
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
@@ -20,3 +21,15 @@ def get_tower_migration_version():
|
|||||||
if migration_version > v:
|
if migration_version > v:
|
||||||
v = migration_version
|
v = migration_version
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_field_names(model):
|
||||||
|
# Implements compatibility with _meta.get_all_field_names
|
||||||
|
# See: https://docs.djangoproject.com/en/1.11/ref/models/meta/#migrating-from-the-old-api
|
||||||
|
return list(set(chain.from_iterable(
|
||||||
|
(field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
|
||||||
|
for field in model._meta.get_fields()
|
||||||
|
# For complete backwards compatibility, you may want to exclude
|
||||||
|
# GenericForeignKey from the results.
|
||||||
|
if not (field.many_to_one and field.related_model is None)
|
||||||
|
)))
|
||||||
|
|||||||
@@ -1,270 +1,295 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
{# Copy of base.html from rest_framework with minor AWX change. #}
|
{# Copy of base.html from rest_framework with minor AWX change. #}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
{% load rest_framework %}
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load rest_framework %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
|
|
||||||
{% block meta %}
|
{% block meta %}
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
<meta name="robots" content="NONE,NOARCHIVE" />
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<title>{% block title %}Django REST framework{% endblock %}</title>
|
|
||||||
|
|
||||||
{% block style %}
|
|
||||||
{% block bootstrap_theme %}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap-tweaks.css" %}"/>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/prettify.css" %}"/>
|
<title>{% block title %}{% if name %}{{ name }} – {% endif %}Django REST framework{% endblock %}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% block style %}
|
||||||
</head>
|
{% block bootstrap_theme %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap-tweaks.css" %}"/>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/prettify.css" %}"/>
|
||||||
<body class="{% block bodyclass %}{% endblock %}">
|
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/>
|
||||||
|
|
||||||
<div class="wrapper">
|
|
||||||
{% block navbar %}
|
|
||||||
<div class="navbar navbar-static-top {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}">
|
|
||||||
<div class="container">
|
|
||||||
<span>
|
|
||||||
{% block branding %}
|
|
||||||
<a class='navbar-brand' rel="nofollow" href='http://www.django-rest-framework.org'>
|
|
||||||
Django REST framework <span class="version">{{ version }}</span>
|
|
||||||
</a>
|
|
||||||
{% endblock %}
|
|
||||||
</span>
|
|
||||||
<ul class="nav navbar-nav pull-right">
|
|
||||||
{% block userlinks %}
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
{% optional_logout request user %}
|
|
||||||
{% else %}
|
|
||||||
{% optional_login request %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
<ul class="breadcrumb">
|
|
||||||
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
|
||||||
{% if forloop.last %}
|
|
||||||
<li class="active"><a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<!-- Content -->
|
{% endblock %}
|
||||||
<div id="content">
|
</head>
|
||||||
|
|
||||||
{% if 'GET' in allowed_methods %}
|
{% block body %}
|
||||||
<form id="get-form" class="pull-right">
|
<body class="{% block bodyclass %}{% endblock %}">
|
||||||
<fieldset>
|
|
||||||
{% if api_settings.URL_FORMAT_OVERRIDE %}
|
|
||||||
<div class="btn-group format-selection">
|
|
||||||
<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="{% trans 'Specify a format for the GET request' %}">
|
<div class="wrapper">
|
||||||
<span class="caret"></span>
|
{% block navbar %}
|
||||||
</button>
|
<div class="navbar navbar-static-top {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}"
|
||||||
<ul class="dropdown-menu">
|
role="navigation" aria-label="{% trans "navbar" %}">
|
||||||
{% for format in available_formats %}
|
<div class="container">
|
||||||
<li>
|
<span>
|
||||||
<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>
|
{% block branding %}
|
||||||
</li>
|
<a class='navbar-brand' rel="nofollow" href='http://www.django-rest-framework.org'>
|
||||||
{% endfor %}
|
Django REST framework
|
||||||
</ul>
|
</a>
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<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 %}
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if options_form %}
|
|
||||||
<form class="button-form" action="{{ request.get_full_path }}" data-method="OPTIONS">
|
|
||||||
<button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make an OPTIONS request on the {{ name }} resource{% endblocktrans %}">OPTIONS</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if delete_form %}
|
|
||||||
<form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE">
|
|
||||||
<button class="btn btn-danger js-tooltip" title="{% blocktrans %}Make a DELETE request on the {{ name }} resource{% endblocktrans %}">DELETE</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if filter_form %}
|
|
||||||
<button style="float: right; margin-right: 10px" data-toggle="modal" data-target="#filtersModal" class="btn btn-default">
|
|
||||||
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
|
||||||
{% trans "Filters" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="content-main">
|
|
||||||
<div class="page-header">
|
|
||||||
<h1>{{ name }}</h1>
|
|
||||||
</div>
|
|
||||||
<div style="float:left">
|
|
||||||
{% block description %}
|
|
||||||
{{ description }}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</span>
|
||||||
|
<ul class="nav navbar-nav pull-right">
|
||||||
|
{% block userlinks %}
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
{% optional_logout request user %}
|
||||||
|
{% else %}
|
||||||
|
{% optional_login request %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% if paginator %}
|
<div class="container">
|
||||||
<nav style="float: right">
|
{% block breadcrumbs %}
|
||||||
{% get_pagination_html paginator %}
|
<ul class="breadcrumb">
|
||||||
</nav>
|
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
||||||
{% endif %}
|
{% if forloop.last %}
|
||||||
|
<li class="active"><a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% empty %}
|
||||||
|
{% block breadcrumbs_empty %} {% endblock breadcrumbs_empty %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<div class="request-info" style="clear: both" >
|
<!-- Content -->
|
||||||
<pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>
|
<div id="content" role="main" aria-label="{% trans "content" %}">
|
||||||
</div>
|
{% block content %}
|
||||||
|
|
||||||
<div class="response-info">
|
<div class="region" aria-label="{% trans "request form" %}">
|
||||||
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
|
{% if 'GET' in allowed_methods %}
|
||||||
{% for key, val in response_headers.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>
|
<form id="get-form" class="pull-right">
|
||||||
{% endfor %}
|
<fieldset>
|
||||||
{# Original line below had the side effect of also escaping content: #}
|
{% if api_settings.URL_FORMAT_OVERRIDE %}
|
||||||
{# </span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %} #}
|
<div class="btn-group format-selection">
|
||||||
{# For Ansible Tower, disable automatic URL creation and move content outside of autoescape off block. #}
|
<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>
|
||||||
{% endautoescape %}</span>{{ content }}</pre>
|
|
||||||
|
<button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request">
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% for format in available_formats %}
|
||||||
|
<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>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% 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>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if options_form %}
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if delete_form %}
|
||||||
|
<button class="btn btn-danger button-form js-tooltip" title="Make a DELETE request on the {{ name }} resource" data-toggle="modal" data-target="#deleteModal">DELETE</button>
|
||||||
|
|
||||||
|
<!-- Delete Modal -->
|
||||||
|
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<h4 class="text-center">Are you sure you want to delete this {{ name }}?</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
<form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE">
|
||||||
|
<button class="btn btn-danger">Delete</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if filter_form %}
|
||||||
|
<button style="float: right; margin-right: 10px" data-toggle="modal" data-target="#filtersModal" class="btn btn-default">
|
||||||
|
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
||||||
|
{% trans "Filters" %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if display_edit_forms %}
|
<div class="content-main" role="main" aria-label="{% trans "main content" %}">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>{{ name }}</h1>
|
||||||
|
</div>
|
||||||
|
<div style="float:left">
|
||||||
|
{% block description %}
|
||||||
|
{{ description }}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if post_form or raw_data_post_form %}
|
{% if paginator %}
|
||||||
<div {% if post_form %}class="tabbable"{% endif %}>
|
<nav style="float: right">
|
||||||
{% if post_form %}
|
{% get_pagination_html paginator %}
|
||||||
<ul class="nav nav-tabs form-switcher">
|
</nav>
|
||||||
<li>
|
{% endif %}
|
||||||
<a name='html-tab' href="#post-object-form" data-toggle="tab">HTML form</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a name='raw-tab' href="#post-generic-content-form" data-toggle="tab">Raw data</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="well tab-content">
|
<div class="request-info" style="clear: both" aria-label="{% trans "request info" %}">
|
||||||
|
<pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="response-info" aria-label="{% trans "response info" %}">
|
||||||
|
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}{% for key, val in response_headers|items %}
|
||||||
|
<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>{% endfor %}
|
||||||
|
{# Original line below had the side effect of also escaping content: #}
|
||||||
|
{# </span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %} #}
|
||||||
|
{# For AWX, disable automatic URL creation and move content outside of autoescape off block. #}
|
||||||
|
{% endautoescape %}</span>{{ content }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if display_edit_forms %}
|
||||||
|
{% if post_form or raw_data_post_form %}
|
||||||
|
<div {% if post_form %}class="tabbable"{% endif %}>
|
||||||
{% if post_form %}
|
{% if post_form %}
|
||||||
<div class="tab-pane" id="post-object-form">
|
<ul class="nav nav-tabs form-switcher">
|
||||||
{% with form=post_form %}
|
<li>
|
||||||
<form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
<a name='html-tab' href="#post-object-form" data-toggle="tab">HTML form</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a name='raw-tab' href="#post-generic-content-form" data-toggle="tab">Raw data</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="well tab-content">
|
||||||
|
{% if post_form %}
|
||||||
|
<div class="tab-pane" id="post-object-form">
|
||||||
|
{% with form=post_form %}
|
||||||
|
<form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||||
|
<fieldset>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ post_form }}
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div {% if post_form %}class="tab-pane"{% endif %} id="post-generic-content-form">
|
||||||
|
{% with form=raw_data_post_form %}
|
||||||
|
<form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
{% csrf_token %}
|
{% include "rest_framework/raw_data_form.html" %}
|
||||||
{{ post_form }}
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button class="btn btn-primary" title="{% blocktrans %}Make a POST request on the {{ name }} resource{% endblocktrans %}">POST</button>
|
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div {% if raw_data_post_form %}class="tab-pane"{% endif %} id="post-generic-content-form">
|
|
||||||
{% with form=raw_data_post_form %}
|
|
||||||
<form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
|
|
||||||
<fieldset>
|
|
||||||
{% include "rest_framework/raw_data_form.html" %}
|
|
||||||
<div class="form-actions">
|
|
||||||
<button class="btn btn-primary" title="{% blocktrans %}Make a POST request on the {{ name }} resource{% endblocktrans %}">POST</button>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if put_form or raw_data_put_form or raw_data_patch_form %}
|
{% if put_form or raw_data_put_form or raw_data_patch_form %}
|
||||||
<div {% if put_form %}class="tabbable"{% endif %}>
|
<div {% if put_form %}class="tabbable"{% endif %}>
|
||||||
{% if put_form %}
|
|
||||||
<ul class="nav nav-tabs form-switcher">
|
|
||||||
<li>
|
|
||||||
<a name='html-tab' href="#put-object-form" data-toggle="tab">HTML form</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a name='raw-tab' href="#put-generic-content-form" data-toggle="tab">Raw data</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="well tab-content">
|
|
||||||
{% if put_form %}
|
{% if put_form %}
|
||||||
<div class="tab-pane" id="put-object-form">
|
<ul class="nav nav-tabs form-switcher">
|
||||||
<form action="{{ request.get_full_path }}" data-method="PUT" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
<li>
|
||||||
<fieldset>
|
<a name='html-tab' href="#put-object-form" data-toggle="tab">HTML form</a>
|
||||||
{{ put_form }}
|
</li>
|
||||||
<div class="form-actions">
|
<li>
|
||||||
<button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PUT request on the {{ name }} resource{% endblocktrans %}">PUT</button>
|
<a name='raw-tab' href="#put-generic-content-form" data-toggle="tab">Raw data</a>
|
||||||
</div>
|
</li>
|
||||||
</fieldset>
|
</ul>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div {% if put_form %}class="tab-pane"{% endif %} id="put-generic-content-form">
|
<div class="well tab-content">
|
||||||
{% with form=raw_data_put_or_patch_form %}
|
{% if put_form %}
|
||||||
<form action="{{ request.get_full_path }}" data-method="PUT" class="form-horizontal">
|
<div class="tab-pane" id="put-object-form">
|
||||||
<fieldset>
|
<form action="{{ request.get_full_path }}" data-method="PUT" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||||
{% include "rest_framework/raw_data_form.html" %}
|
<fieldset>
|
||||||
<div class="form-actions">
|
{{ put_form }}
|
||||||
{% if raw_data_put_form %}
|
<div class="form-actions">
|
||||||
<button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PUT request on the {{ name }} resource{% endblocktrans %}">PUT</button>
|
<button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button>
|
||||||
{% endif %}
|
</div>
|
||||||
{% if raw_data_patch_form %}
|
</fieldset>
|
||||||
<button data-method="PATCH" class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PATCH request on the {{ name }} resource{% endblocktrans %}">PATCH</button>
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div {% if put_form %}class="tab-pane"{% endif %} id="put-generic-content-form">
|
||||||
|
{% with form=raw_data_put_or_patch_form %}
|
||||||
|
<form action="{{ request.get_full_path }}" data-method="PUT" class="form-horizontal">
|
||||||
|
<fieldset>
|
||||||
|
{% include "rest_framework/raw_data_form.html" %}
|
||||||
|
<div class="form-actions">
|
||||||
|
{% if raw_data_put_form %}
|
||||||
|
<button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% if raw_data_patch_form %}
|
||||||
</fieldset>
|
<button data-method="PATCH" class="btn btn-primary js-tooltip" title="Make a PATCH request on the {{ name }} resource">PATCH</button>
|
||||||
</form>
|
{% endif %}
|
||||||
{% endwith %}
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endblock content %}
|
||||||
</div><!-- /.content -->
|
</div><!-- /.content -->
|
||||||
</div><!-- /.container -->
|
</div><!-- /.container -->
|
||||||
{# div#push added for Ansible Tower. #}
|
</div><!-- ./wrapper -->
|
||||||
<div id="push"></div>
|
|
||||||
</div><!-- ./wrapper -->
|
|
||||||
|
|
||||||
{% block script %}
|
{% if filter_form %}
|
||||||
<script src="{% static "rest_framework/js/jquery-1.11.3.min.js" %}"></script>
|
{{ filter_form }}
|
||||||
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script>
|
{% endif %}
|
||||||
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
|
|
||||||
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
|
{% block script %}
|
||||||
<script src="{% static "rest_framework/js/prettify-min.js" %}"></script>
|
<script>
|
||||||
<script src="{% static "rest_framework/js/default.js" %}"></script>
|
window.drf = {
|
||||||
<script>
|
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}",
|
||||||
|
csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}"
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="{% static "rest_framework/js/jquery-1.12.4.min.js" %}"></script>
|
||||||
|
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script>
|
||||||
|
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
|
||||||
|
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
|
||||||
|
<script src="{% static "rest_framework/js/prettify-min.js" %}"></script>
|
||||||
|
<script src="{% static "rest_framework/js/default.js" %}"></script>
|
||||||
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('form').ajaxForm();
|
$('form').ajaxForm();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</body>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% if filter_form %}
|
|
||||||
{{ filter_form }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</body>
|
|
||||||
{% endblock %}
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user