Added rudimentary admin test cases
parent
8caac2e57b
commit
8d4cb9b151
|
|
@ -0,0 +1,197 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
from django.contrib.admin import AdminSite
|
||||||
|
from django.contrib.admin.templatetags.admin_urls import admin_urlname
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.messages.middleware import MessageMiddleware
|
||||||
|
from django.test import RequestFactory, TestCase
|
||||||
|
from django.urls import clear_url_caches, reverse, set_urlconf
|
||||||
|
|
||||||
|
|
||||||
|
class AdminTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Testing the admin site
|
||||||
|
"""
|
||||||
|
#: The model to test
|
||||||
|
model = None
|
||||||
|
#: The admin class to test
|
||||||
|
admin_class = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(AdminTestCase, cls).setUpClass()
|
||||||
|
cls.admin_user = User.objects.create_superuser('admin', 'admin@example.org', password='admin')
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(AdminTestCase, self).setUp()
|
||||||
|
|
||||||
|
# Have a separate site, to avoid dependency on polymorphic wrapping or standard admin configuration
|
||||||
|
self.admin_site = AdminSite()
|
||||||
|
|
||||||
|
if self.model is not None:
|
||||||
|
self.admin_register(self.model, self.admin_class)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
clear_url_caches()
|
||||||
|
set_urlconf(None)
|
||||||
|
|
||||||
|
def register(self, model):
|
||||||
|
"""Decorator, like admin.register()"""
|
||||||
|
def _dec(admin_class):
|
||||||
|
self.admin_register(model, admin_class)
|
||||||
|
return admin_class
|
||||||
|
return _dec
|
||||||
|
|
||||||
|
def admin_register(self, model, admin_site):
|
||||||
|
"""Register an model with admin to the test case, test client and URL reversing code."""
|
||||||
|
self.admin_site.register(model, admin_site)
|
||||||
|
|
||||||
|
# Make sure the URLs are reachable by reverse()
|
||||||
|
clear_url_caches()
|
||||||
|
set_urlconf(tuple([
|
||||||
|
url('^', include(self.admin_site.urls))
|
||||||
|
]))
|
||||||
|
|
||||||
|
def get_admin_instance(self, model):
|
||||||
|
try:
|
||||||
|
return self.admin_site._registry[model]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError("Model not registered with admin: {}".format(model))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
super(AdminTestCase, cls).tearDownClass()
|
||||||
|
clear_url_caches()
|
||||||
|
set_urlconf(None)
|
||||||
|
|
||||||
|
def get_add_url(self, model):
|
||||||
|
admin_instance = self.get_admin_instance(model)
|
||||||
|
return reverse(admin_urlname(admin_instance.opts, 'add'))
|
||||||
|
|
||||||
|
def get_changelist_url(self, model):
|
||||||
|
admin_instance = self.get_admin_instance(model)
|
||||||
|
return reverse(admin_urlname(admin_instance.opts, 'changelist'))
|
||||||
|
|
||||||
|
def get_change_url(self, model, object_id):
|
||||||
|
admin_instance = self.get_admin_instance(model)
|
||||||
|
return reverse(admin_urlname(admin_instance.opts, 'change'), args=(object_id,))
|
||||||
|
|
||||||
|
def get_delete_url(self, model, object_id):
|
||||||
|
admin_instance = self.get_admin_instance(model)
|
||||||
|
return reverse(admin_urlname(admin_instance.opts, 'delete'), args=(object_id,))
|
||||||
|
|
||||||
|
def admin_post_add(self, model, formdata):
|
||||||
|
"""
|
||||||
|
Make a direct "add" call to the admin page, circumvening login checks.
|
||||||
|
"""
|
||||||
|
admin_instance = self.get_admin_instance(model)
|
||||||
|
request = self.create_admin_request('post', self.get_add_url(model), data=formdata)
|
||||||
|
response = admin_instance.add_view(request)
|
||||||
|
self.assertFormSuccess(request.path, response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def admin_get_changelist(self, model):
|
||||||
|
"""
|
||||||
|
Make a direct "add" call to the admin page, circumvening login checks.
|
||||||
|
"""
|
||||||
|
admin_instance = self.get_admin_instance(model)
|
||||||
|
request = self.create_admin_request('get', self.get_changelist_url(model))
|
||||||
|
response = admin_instance.changelist_view(request)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def admin_get_change(self, model, object_id, query=None, **extra):
|
||||||
|
"""
|
||||||
|
Perform a GET request on the admin page
|
||||||
|
"""
|
||||||
|
admin_instance = self.get_admin_instance(model)
|
||||||
|
request = self.create_admin_request('get', self.get_change_url(model, object_id), data=query, **extra)
|
||||||
|
response = admin_instance.change_view(request, str(object_id))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def admin_post_change(self, model, object_id, formdata, **extra):
|
||||||
|
"""
|
||||||
|
Make a direct "add" call to the admin page, circumvening login checks.
|
||||||
|
"""
|
||||||
|
admin_instance = self.get_admin_instance(model)
|
||||||
|
request = self.create_admin_request('post', self.get_change_url(model, object_id), data=formdata, **extra)
|
||||||
|
response = admin_instance.change_view(request, str(object_id))
|
||||||
|
self.assertFormSuccess(request.path, response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def admin_post_delete(self, model, object_id, **extra):
|
||||||
|
"""
|
||||||
|
Make a direct "add" call to the admin page, circumvening login checks.
|
||||||
|
"""
|
||||||
|
admin_instance = self.get_admin_instance(model)
|
||||||
|
request = self.create_admin_request('post', self.get_delete_url(model, object_id), **extra)
|
||||||
|
response = admin_instance.delete_view(request, str(object_id))
|
||||||
|
self.assertEqual(response.status_code, 302, "Form errors in calling {0}".format(request.path))
|
||||||
|
return response
|
||||||
|
|
||||||
|
def create_admin_request(self, method, url, data=None, **extra):
|
||||||
|
"""
|
||||||
|
Construct an Request instance for the admin view.
|
||||||
|
"""
|
||||||
|
factory_method = getattr(RequestFactory(), method)
|
||||||
|
|
||||||
|
if data is not None:
|
||||||
|
if method != 'get':
|
||||||
|
data['csrfmiddlewaretoken'] = 'foo'
|
||||||
|
dummy_request = factory_method(url, data=data)
|
||||||
|
dummy_request.user = self.admin_user
|
||||||
|
|
||||||
|
# Add the management form fields if needed.
|
||||||
|
# base_data = self._get_management_form_data(dummy_request)
|
||||||
|
# base_data.update(data)
|
||||||
|
# data = base_data
|
||||||
|
|
||||||
|
request = factory_method(url, data=data, **extra)
|
||||||
|
request.COOKIES[settings.CSRF_COOKIE_NAME] = 'foo'
|
||||||
|
request.csrf_processing_done = True
|
||||||
|
|
||||||
|
# Add properties which middleware would typically do
|
||||||
|
request.session = {}
|
||||||
|
request.user = self.admin_user
|
||||||
|
MessageMiddleware().process_request(request)
|
||||||
|
return request
|
||||||
|
|
||||||
|
def _get_management_form_data(self, admin_instance, request):
|
||||||
|
"""
|
||||||
|
Return the formdata that the management forms need.
|
||||||
|
"""
|
||||||
|
inline_instances = admin_instance.get_inline_instances(request)
|
||||||
|
forms = []
|
||||||
|
for inline_instance in inline_instances:
|
||||||
|
FormSet = inline_instance.get_formset(request)
|
||||||
|
formset = FormSet(instance=admin_instance.model())
|
||||||
|
forms.append(formset.management_form)
|
||||||
|
|
||||||
|
# In a primitive way, get the form fields.
|
||||||
|
# This is not exactly the same as a POST, since that runs through clean()
|
||||||
|
formdata = {}
|
||||||
|
for form in forms:
|
||||||
|
for boundfield in form:
|
||||||
|
formdata[boundfield.html_name] = boundfield.value()
|
||||||
|
|
||||||
|
return formdata
|
||||||
|
|
||||||
|
def assertFormSuccess(self, request_url, response):
|
||||||
|
"""
|
||||||
|
Assert that the response was a redirect, not a form error.
|
||||||
|
"""
|
||||||
|
self.assertIn(response.status_code, [200, 302])
|
||||||
|
if response.status_code != 302:
|
||||||
|
context_data = response.context_data
|
||||||
|
if 'errors' in context_data:
|
||||||
|
errors = response.context_data['errors']
|
||||||
|
elif 'form' in context_data:
|
||||||
|
errors = context_data['form'].errors
|
||||||
|
else:
|
||||||
|
raise KeyError("Unknown field for errors in the TemplateResponse!")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302,
|
||||||
|
"Form errors in calling {0}:\n{1}".format(request_url, errors.as_text()))
|
||||||
|
self.assertTrue('/login/?next=' not in response['Location'],
|
||||||
|
"Received login response for {0}".format(request_url))
|
||||||
|
|
@ -1005,4 +1005,54 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
bases=('tests.model2c',),
|
bases=('tests.model2c',),
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='InlineModelBase',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='InlineParent',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=10)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='InlineModelA',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('field1', models.CharField(max_length=10)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='InlineModelB',
|
||||||
|
fields=[
|
||||||
|
('inlinemodela_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.InlineModelA')),
|
||||||
|
('field2', models.CharField(max_length=10)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('tests.inlinemodela',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inlinemodela',
|
||||||
|
name='parent',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.InlineParent'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inlinemodela',
|
||||||
|
name='polymorphic_ctype',
|
||||||
|
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_tests.inlinemodela_set+', to='contenttypes.ContentType'),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -414,3 +414,16 @@ class SwappableModel(AbstractModel):
|
||||||
|
|
||||||
class SwappedModel(AbstractModel):
|
class SwappedModel(AbstractModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InlineParent(models.Model):
|
||||||
|
title = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
||||||
|
class InlineModelA(PolymorphicModel):
|
||||||
|
parent = models.ForeignKey(InlineParent)
|
||||||
|
field1 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
||||||
|
class InlineModelB(InlineModelA):
|
||||||
|
field2 = models.CharField(max_length=10)
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,27 @@
|
||||||
from django.contrib.admin import AdminSite
|
from django.contrib import admin
|
||||||
from django.test import TestCase
|
from django.utils.html import escape
|
||||||
|
|
||||||
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
|
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicInlineSupportMixin, \
|
||||||
from polymorphic.tests.models import Model2A, Model2B, Model2C, Model2D
|
PolymorphicParentModelAdmin, StackedPolymorphicInline
|
||||||
|
from polymorphic.tests.admintestcase import AdminTestCase
|
||||||
|
from polymorphic.tests.models import InlineModelA, InlineModelB, InlineParent, Model2A, Model2B, Model2C, Model2D
|
||||||
|
|
||||||
|
|
||||||
class MultipleDatabasesTests(TestCase):
|
class PolymorphicAdminTests(AdminTestCase):
|
||||||
|
|
||||||
def test_admin_registration(self):
|
def test_admin_registration(self):
|
||||||
"""
|
"""
|
||||||
Test how the registration works
|
Test how the registration works
|
||||||
"""
|
"""
|
||||||
|
@self.register(Model2A)
|
||||||
class Model2Admin(PolymorphicParentModelAdmin):
|
class Model2Admin(PolymorphicParentModelAdmin):
|
||||||
base_model = Model2A
|
base_model = Model2A
|
||||||
list_filter = (PolymorphicChildModelFilter,)
|
list_filter = (PolymorphicChildModelFilter,)
|
||||||
child_models = (Model2B, Model2C, Model2D)
|
child_models = (Model2B, Model2C, Model2D)
|
||||||
|
|
||||||
|
@self.register(Model2B)
|
||||||
|
@self.register(Model2C)
|
||||||
|
@self.register(Model2D)
|
||||||
class Model2ChildAdmin(PolymorphicChildModelAdmin):
|
class Model2ChildAdmin(PolymorphicChildModelAdmin):
|
||||||
base_model = Model2A
|
base_model = Model2A
|
||||||
base_fieldsets = (
|
base_fieldsets = (
|
||||||
|
|
@ -24,8 +30,39 @@ class MultipleDatabasesTests(TestCase):
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
admin_site = AdminSite()
|
# Now test which results are returned
|
||||||
admin_site.register(Model2A, Model2Admin)
|
d_obj = Model2D.objects.create(field1='A', field2='B', field3='C', field4='D')
|
||||||
admin_site.register(Model2B, Model2ChildAdmin)
|
self.admin_get_changelist(Model2A) # asserts 200
|
||||||
admin_site.register(Model2C, Model2ChildAdmin)
|
|
||||||
admin_site.register(Model2D, Model2ChildAdmin)
|
# See that the child object was returned
|
||||||
|
response = self.admin_get_change(Model2A, d_obj.pk)
|
||||||
|
self.assertContains(response, 'field4')
|
||||||
|
|
||||||
|
def test_admin_inlines(self):
|
||||||
|
"""
|
||||||
|
Test the registration of inline models.
|
||||||
|
"""
|
||||||
|
class InlineModelAChild(StackedPolymorphicInline.Child):
|
||||||
|
model = InlineModelA
|
||||||
|
|
||||||
|
class InlineModelBChild(StackedPolymorphicInline.Child):
|
||||||
|
model = InlineModelB
|
||||||
|
|
||||||
|
class Inline(StackedPolymorphicInline):
|
||||||
|
model = InlineModelA
|
||||||
|
child_inlines = (
|
||||||
|
InlineModelAChild,
|
||||||
|
InlineModelBChild,
|
||||||
|
)
|
||||||
|
|
||||||
|
@self.register(InlineParent)
|
||||||
|
class InlineParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):
|
||||||
|
inlines = (Inline,)
|
||||||
|
|
||||||
|
obj = InlineParent.objects.create(title='FOO')
|
||||||
|
response = self.admin_get_change(InlineParent, obj.pk)
|
||||||
|
|
||||||
|
# Make sure the fieldset has the right data exposed in data-inline-formset
|
||||||
|
self.assertContains(response, 'childTypes')
|
||||||
|
self.assertContains(response, escape('"type": "inlinemodela"'))
|
||||||
|
self.assertContains(response, escape('"type": "inlinemodelb"'))
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ if not settings.configured:
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
POLYMORPHIC_TEST_SWAPPABLE='polymorphic.swappedmodel',
|
POLYMORPHIC_TEST_SWAPPABLE='polymorphic.swappedmodel',
|
||||||
|
ROOT_URLCONF=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue