Skip to content

Commit

Permalink
Compatibility fixes for Django 1.10 compatibility, stronger tests, ex…
Browse files Browse the repository at this point in the history
…clude_modeladmins option (#8)

* remove old queryset testing

since Django<1.8 support was dropped, so this is not tested anymore
it breaks tests when queryset method is defined on modeladmin for compatibility reasons

* stronger tests

render respose
add change_view test

* add exclude_modeladmins option

* test also search request to identify bad search_fields settings

* test in Django 1.10; show all warnings

* fix tests in Django 1.10

* test that exceptions are raised, throw own exception class, increase coverage

* include python 3.4, django 1.7 Travis test

* remove old deprecated code

* test and fix catching PermissionDenied exception - use it where it is needed

* fix backward compatibility with Python 2.7

* print python version to ensure correct testing

* test request.GET parameter

* check import order with flake8; fix all import orders

* use isinstance instead of __class__

* run tests with USE_TZ=True (add pytz dependency to tox)

* fix tox Django version order

* test also some aspects of posting change_view
  • Loading branch information
PetrDlouhy authored and SeanHayes committed Nov 3, 2016
1 parent 53ecdfa commit 746382c
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 33 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ env:
- DJANGO_VERSION=dj17
- DJANGO_VERSION=dj18
- DJANGO_VERSION=dj19
- DJANGO_VERSION=dj110
- DJANGO_VERSION=djdev

matrix:
Expand All @@ -21,6 +22,8 @@ matrix:
include:
- python: "2.7"
env: MODE=flake8
- python: "3.4"
env: DJANGO_VERSION=dj17

cache:
directories:
Expand Down
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ you can do following:
class AdminSiteSmokeTest(AdminSiteSmokeTestMixin, TestCase):
fixtures = ['data']
And you can exclude certain (external) apps with::
And you can exclude certain (external) apps or model admins with::

exclude_apps = ['constance',]
exclude_modeladmins = [apps.admin.ModelAdmin]
105 changes: 87 additions & 18 deletions django_admin_smoke_tests/tests.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
import six
import django

from django.contrib import admin, auth
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied,\
ValidationError
from django.http.request import QueryDict
from django.test import TestCase
from django.test.client import RequestFactory
import sys

import six


class ModelAdminCheckException(Exception):
def __init__(self, message, original_exception):
self.original_exception = original_exception
return super(ModelAdminCheckException, self).__init__(message)


def for_all_model_admins(fn):
def test_deco(self):
for model, model_admin in self.modeladmins:
if model_admin.__class__ in self.exclude_modeladmins:
continue
if model._meta.app_label in self.exclude_apps:
continue
try:
fn(self, model, model_admin)
except Exception:
raise Exception(
"Above exception occured while running test '%s'"
except Exception as e:
six.raise_from(ModelAdminCheckException(
"Above exception occured while running test '%s' "
"on modeladmin %s (%s)" %
(fn.__name__, model_admin, model.__name__)).\
with_traceback(sys.exc_info()[2])
(fn.__name__, model_admin, model.__name__),
e), e)
return test_deco


class AdminSiteSmokeTestMixin(object):
modeladmins = None
exclude_apps = []
exclude_modeladmins = []
fixtures = ['django_admin_smoke_tests']

single_attributes = ['date_hierarchy']
Expand Down Expand Up @@ -63,22 +75,27 @@ def setUp(self):
except:
pass

def get_request(self):
request = self.factory.get('/')
def get_request(self, params=None):
request = self.factory.get('/', params)

request.user = self.superuser
return request

def post_request(self, post_data={}, params=None):
request = self.factory.post('/', params, post_data=post_data)

request.user = self.superuser
request._dont_enforce_csrf_checks = True
return request

def strip_minus(self, attr, val):
if attr in self.strip_minus_attrs and val[0] == '-':
val = val[1:]
return val

def get_fieldsets(self, model, model_admin):
request = self.get_request()
try:
return model_admin.get_fieldsets(request, obj=model())
except AttributeError:
return model_admin.declared_fieldsets
return model_admin.get_fieldsets(request, obj=model())

def get_attr_set(self, model, model_admin):
attr_set = []
Expand Down Expand Up @@ -163,8 +180,6 @@ def test_queryset(self, model, model_admin):

# TODO: use model_mommy to generate a few instances to query against
# make sure no errors happen here
if hasattr(model_admin, 'queryset'):
list(model_admin.queryset(request))
if hasattr(model_admin, 'get_queryset'):
list(model_admin.get_queryset(request))

Expand All @@ -184,8 +199,28 @@ def test_changelist_view(self, model, model_admin):
request = self.get_request()

# make sure no errors happen here
response = model_admin.changelist_view(request)
self.assertEqual(response.status_code, 200)
try:
response = model_admin.changelist_view(request)
response.render()
self.assertEqual(response.status_code, 200)
except PermissionDenied:
# this error is commonly raised by ModelAdmins that don't allow
# changelist view
pass

@for_all_model_admins
def test_changelist_view_search(self, model, model_admin):
request = self.get_request(params=QueryDict('q=test'))

# make sure no errors happen here
try:
response = model_admin.changelist_view(request)
response.render()
self.assertEqual(response.status_code, 200)
except PermissionDenied:
# this error is commonly raised by ModelAdmins that don't allow
# changelist view.
pass

@for_all_model_admins
def test_add_view(self, model, model_admin):
Expand All @@ -194,12 +229,46 @@ def test_add_view(self, model, model_admin):
# make sure no errors happen here
try:
response = model_admin.add_view(request)
if isinstance(response, django.template.response.TemplateResponse):
response.render()
self.assertEqual(response.status_code, 200)
except PermissionDenied:
# this error is commonly raised by ModelAdmins that don't allow
# adding.
pass

@for_all_model_admins
def test_change_view(self, model, model_admin):
item = model.objects.last()
if not item or model._meta.proxy:
return
pk = item.pk
request = self.get_request()

# make sure no errors happen here
response = model_admin.change_view(request, object_id=str(pk))
if isinstance(response, django.template.response.TemplateResponse):
response.render()
self.assertEqual(response.status_code, 200)

@for_all_model_admins
def test_change_post(self, model, model_admin):
item = model.objects.last()
if not item or model._meta.proxy:
return
pk = item.pk
# TODO: If we generate default post_data for post request,
# the test would be stronger
request = self.post_request()
try:
response = model_admin.change_view(request, object_id=str(pk))
if isinstance(response, django.template.response.TemplateResponse):
response.render()
self.assertEqual(response.status_code, 200)
except ValidationError:
# This the form was sent, but did not pass it's validation
pass


class AdminSiteSmokeTest(AdminSiteSmokeTestMixin, TestCase):
pass
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from setuptools import setup
import django_admin_smoke_tests

from setuptools import setup

package_name = 'django_admin_smoke_tests'


Expand All @@ -17,7 +18,7 @@ def runtests():
os.environ['DJANGO_SETTINGS_MODULE'] = 'test_project.settings'
if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
django.setup()
call_command('test', 'django_admin_smoke_tests')
call_command('test', 'test_project.main.tests')
sys.exit()

setup(name='django-admin-smoke-tests',
Expand Down
48 changes: 46 additions & 2 deletions test_project/main/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Django imports
import django

from django.contrib import admin
from django.contrib.admin import SimpleListFilter

# App imports
from .models import Channel, HasPrimarySlug, HasPrimaryUUID, Post
from .models import Channel, FailPost, ForbiddenPost,\
HasPrimarySlug, HasPrimaryUUID, Post


class ChannelAdmin(admin.ModelAdmin):
Expand All @@ -11,14 +15,33 @@ class ChannelAdmin(admin.ModelAdmin):
admin.site.register(Channel, ChannelAdmin)


class ListFilter(SimpleListFilter):
title = "list_filter"
parameter_name = "list_filter"

def __init__(self, request, params, model, model_admin):
super(ListFilter, self).__init__(request, params, model, model_admin)
self.lookup_val = request.GET.getlist('a')

def lookups(self, request, model_admin):
return ()

def queryset(self, request, queryset):
return queryset


class PostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
list_editable = ['status']
list_display = ('title', 'author', 'status', 'modified', 'published',)
list_filter = ('author', 'status', 'channel', 'created', 'modified',
'published',)
'published', ListFilter)
readonly_fields = ['created', 'modified', 'time_diff']
ordering = ('title', '-id',)
fieldsets = [('Fielset', {
'fields': ['created', ('slug', 'title', 'author', 'status')]}),
]
date_hierarchy = 'created'

search_fields = ['title', 'text']

Expand All @@ -28,7 +51,28 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs):
return super(PostAdmin, self).formfield_for_foreignkey(db_field,
request, **kwargs)


class FailPostAdmin(admin.ModelAdmin):
search_fields = ['nonexistent_field']

if django.VERSION >= (1, 8):
list_display = ['nonexistent_field']


class ForbiddenPostAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
False

def has_change_permission(self, request, obj=None):
False

def has_delete_permission(self, request):
False


admin.site.register(Post, PostAdmin)
admin.site.register(FailPost, FailPostAdmin)
admin.site.register(ForbiddenPost, ForbiddenPostAdmin)


admin.site.register(HasPrimarySlug)
Expand Down
10 changes: 9 additions & 1 deletion test_project/main/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import uuid

# Django imports
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.utils import timezone
from django.conf import settings


class _Abstract(models.Model):
Expand Down Expand Up @@ -83,6 +83,14 @@ def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})


class ForbiddenPost(Post):
pass


class FailPost(Post):
pass


class HasPrimarySlug(models.Model):
slug = models.SlugField(primary_key=True)
title = models.CharField(max_length=140, unique=True)
Expand Down
38 changes: 38 additions & 0 deletions test_project/main/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import django

from django.test import TestCase
from django_admin_smoke_tests.tests import AdminSiteSmokeTestMixin,\
ModelAdminCheckException, for_all_model_admins
from .admin import ChannelAdmin, FailPostAdmin, ForbiddenPostAdmin, PostAdmin


class AdminSiteSmokeTest(AdminSiteSmokeTestMixin, TestCase):
fixtures = []
exclude_apps = ['auth']
exclude_modeladmins = [FailPostAdmin, ForbiddenPostAdmin]


class FailAdminSiteSmokeTest(AdminSiteSmokeTestMixin, TestCase):
fixtures = []
exclude_modeladmins = [ForbiddenPostAdmin, PostAdmin, ChannelAdmin]

@for_all_model_admins
def test_specified_fields(self, model, model_admin):
with self.assertRaises(ModelAdminCheckException):
super(FailAdminSiteSmokeTest, self).test_specified_fields()

@for_all_model_admins
def test_changelist_view_search(self, model, model_admin):
with self.assertRaises(ModelAdminCheckException):
super(FailAdminSiteSmokeTest, self).test_changelist_view_search()

if django.VERSION >= (1, 8):
@for_all_model_admins
def test_changelist_view(self, model, model_admin):
with self.assertRaises(ModelAdminCheckException):
super(FailAdminSiteSmokeTest, self).test_changelist_view()


class ForbiddenAdminSiteSmokeTest(AdminSiteSmokeTestMixin, TestCase):
fixtures = []
exclude_modeladmins = [FailPostAdmin, PostAdmin, ChannelAdmin]
19 changes: 18 additions & 1 deletion test_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,24 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

TEMPLATE_DEBUG = True
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
'debug': True,
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
],
},
},
]

ALLOWED_HOSTS = []

Expand Down
Loading

0 comments on commit 746382c

Please sign in to comment.