diff --git a/.gitignore b/.gitignore
index 9873c61..34b0fbf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,9 @@ pip-log.txt
.tox
nosetests.xml
+# Test cache
+/.pytest_cache
+
# Translations
*.mo
@@ -45,4 +48,4 @@ docs/_build
output/
#PyCharm
-.idea/
\ No newline at end of file
+.idea/
diff --git a/.travis.yml b/.travis.yml
index ddd05cb..ae8baf9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,12 +4,9 @@ language: python
python:
- "3.5"
- - "3.4"
- - "2.7"
env:
- - DJANGO="Django==1.8.4"
- - DJANGO="Django==1.9.9"
- DJANGO="Django==1.10"
+ - DJANGO="Django==2.0.7"
# command to install dependencies, e.g. pip install -r requirements.txt
install:
@@ -17,7 +14,7 @@ install:
- pip install -r requirements-test.txt
# command to run tests using coverage, e.g. python setup.py test
-script: coverage run --source admin_reorder runtests.py
+script: python -m pytest --cov=admin_reorder
# report coverage to coveralls.io
after_success: coveralls
diff --git a/admin_reorder/__init__.py b/admin_reorder/__init__.py
index e1424ed..73e3bb4 100644
--- a/admin_reorder/__init__.py
+++ b/admin_reorder/__init__.py
@@ -1 +1 @@
-__version__ = '0.3.1'
+__version__ = '0.3.2'
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..929815d
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+DJANGO_SETTINGS_MODULE=tests.settings
diff --git a/requirements-test.txt b/requirements-test.txt
index ed7dea2..e1ec329 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -8,3 +8,6 @@ django-nose>=1.2
flake8>=2.1.0
tox>=1.7.0
+pytest==3.6.3
+pytest-django==3.3.2
+pytest-cov==2.5.1
diff --git a/runtests.py b/runtests.py
deleted file mode 100644
index 396efbe..0000000
--- a/runtests.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import sys
-
-try:
- from django.conf import settings
-
- settings.configure(
- DEBUG=True,
- USE_TZ=True,
- DATABASES={
- "default": {
- "ENGINE": "django.db.backends.sqlite3",
- }
- },
- ROOT_URLCONF="admin_reorder.urls",
- INSTALLED_APPS=[
- "django.contrib.auth",
- "django.contrib.contenttypes",
- "django.contrib.sites",
- "admin_reorder",
- ],
- SITE_ID=1,
- NOSE_ARGS=['-s'],
- )
-
- try:
- import django
- setup = django.setup
- except AttributeError:
- pass
- else:
- setup()
-
- from django_nose import NoseTestSuiteRunner
-except ImportError:
- raise ImportError("To fix this error, run: pip install -r requirements-test.txt")
-
-
-def run_tests(*test_args):
- if not test_args:
- test_args = ['tests']
-
- # Run tests
- test_runner = NoseTestSuiteRunner(verbosity=1)
-
- failures = test_runner.run_tests(test_args)
-
- if failures:
- sys.exit(failures)
-
-
-if __name__ == '__main__':
- run_tests(*sys.argv[1:])
diff --git a/tests/app1/__init__.py b/tests/app1/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/app1/admin.py b/tests/app1/admin.py
new file mode 100644
index 0000000..90dce55
--- /dev/null
+++ b/tests/app1/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+
+from tests.app1.models import App1Model1, App1Model2
+
+
+@admin.register(App1Model1)
+class App1Model1Admin(admin.ModelAdmin):
+ pass
+
+
+@admin.register(App1Model2)
+class App1Model2Admin(admin.ModelAdmin):
+ pass
diff --git a/tests/app1/models.py b/tests/app1/models.py
new file mode 100644
index 0000000..31488d7
--- /dev/null
+++ b/tests/app1/models.py
@@ -0,0 +1,9 @@
+from django.db import models
+
+
+class App1Model1(models.Model):
+ field = models.BigIntegerField()
+
+
+class App1Model2(models.Model):
+ field = models.BigIntegerField()
diff --git a/tests/app2/__init__.py b/tests/app2/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/app2/admin.py b/tests/app2/admin.py
new file mode 100644
index 0000000..39081d2
--- /dev/null
+++ b/tests/app2/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+
+from tests.app2.models import App2Model1, App2Model2
+
+
+@admin.register(App2Model1)
+class App2Model1Admin(admin.ModelAdmin):
+ pass
+
+
+@admin.register(App2Model2)
+class App2Model2Admin(admin.ModelAdmin):
+ pass
diff --git a/tests/app2/models.py b/tests/app2/models.py
new file mode 100644
index 0000000..b51846c
--- /dev/null
+++ b/tests/app2/models.py
@@ -0,0 +1,9 @@
+from django.db import models
+
+
+class App2Model1(models.Model):
+ field = models.BigIntegerField()
+
+
+class App2Model2(models.Model):
+ field = models.BigIntegerField()
diff --git a/tests/settings.py b/tests/settings.py
new file mode 100644
index 0000000..744e328
--- /dev/null
+++ b/tests/settings.py
@@ -0,0 +1,33 @@
+DEBUG = True
+USE_TZ = True
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ }
+}
+ROOT_URLCONF = 'admin_reorder.urls'
+INSTALLED_APPS = [
+ 'django.contrib.auth',
+ 'django.contrib.admin',
+ 'django.contrib.contenttypes',
+ 'django.contrib.messages',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'tests.app1',
+ 'tests.app2',
+ 'admin_reorder',
+]
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.contrib.auth.context_processors.auth',
+ ]
+ }
+ }
+]
+SITE_ID = 1
+SECRET_KEY = 'yolo'
+ROOT_URLCONF = 'tests.urls'
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
new file mode 100644
index 0000000..928a829
--- /dev/null
+++ b/tests/test_middleware.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+import re
+import unittest
+
+from django.contrib.auth.models import User
+from django.core.exceptions import ImproperlyConfigured
+from django.test import RequestFactory, override_settings
+try:
+ from django.urls import resolve
+except ImportError:
+ # Backwards compatibility with Django<1.10
+ from django.core.urlresolvers import resolve
+from pytest import raises, mark
+
+from admin_reorder.middleware import ModelAdminReorder
+from tests.app1.models import App1Model1, App1Model2
+from tests.app2.models import App2Model1, App2Model2
+
+
+@mark.django_db()
+class ModelAdminReorderTest(unittest.TestCase):
+ """Tests for the ModelAdminReorder middleware class."""
+ def setUp(self):
+ self.superuser = User.objects.create(is_staff=True, is_superuser=True)
+
+ def _make_request(self, path):
+ """Generates a mocked request by a staff user for a given path."""
+ request = RequestFactory().get(path)
+ request.user = self.superuser
+ return request
+
+ @staticmethod
+ def _get_response_for_path(request, path):
+ """Executes the view at path and returns the response."""
+ return resolve(path).func(request)
+
+ def _render_admin_index(self):
+ path = '/admin/'
+ request = self._make_request(path)
+ response = self._get_response_for_path(request, path)
+
+ return request, response
+
+ def test_require_configuration(self):
+ """
+ Throw an ImproperlyConfigured error if the ADMIN_REORDER config is missing when rendering the admin.
+ """
+ request, response = self._render_admin_index()
+
+ with raises(ImproperlyConfigured) as exc_info:
+ ModelAdminReorder().process_template_response(request, response)
+
+ assert str(exc_info.value) == 'ADMIN_REORDER config is not defined.'
+
+ def _assert_model_prescence_in_response(self, models_to_expect, response):
+ """
+ Given an unrendered admin index response, check that every model in the given list is mentioned there.
+ """
+ decoded_content = response.content.decode('utf8')
+
+ for model_to_expect in models_to_expect:
+ lower_case_model_name = model_to_expect.__name__.lower()
+ assert re.search(
+ ''.format(lower_case_model_name),
+ decoded_content,
+ ), lower_case_model_name + ' not present'
+
+ def _assert_model_absence_in_response(self, models_to_be_absent, response):
+ """
+ Given an admin index response, check that every model in the given list is missing there.
+ """
+ decoded_content = response.content.decode('utf8')
+
+ for model_to_be_absent in models_to_be_absent:
+ lower_case_model_name = model_to_be_absent.__name__.lower()
+ assert not re.search(
+ ''.format(lower_case_model_name),
+ decoded_content,
+ ), lower_case_model_name + ' not absent'
+
+ def _render_with_configuration_and_check_model_presence(self, config, models_to_expect, models_to_be_absent=None):
+ """
+ Renders the admin index with `config` as the ADMIN_REORDER setting, checks that certain models are
+ mentioned on the page, and checks that certain models are absent from the page.
+ """
+ with override_settings(ADMIN_REORDER=config):
+ request, response = self._render_admin_index()
+ ModelAdminReorder().process_template_response(request, response)
+ response.render()
+
+ # Check that the right models are present/absent
+ self._assert_model_prescence_in_response(models_to_expect, response)
+ if models_to_be_absent:
+ self._assert_model_absence_in_response(models_to_be_absent, response)
+
+ def test_only_valid_apps(self):
+ """When the config only contains valid app names, the models should be present."""
+ self._render_with_configuration_and_check_model_presence(
+ config=(
+ 'app1',
+ 'app2',
+ ),
+ models_to_expect=[App1Model1, App1Model2, App2Model1, App2Model2],
+ )
+
+ def test_custom_grouping(self):
+ """When the config only uses custom grouping, the index should render."""
+ self._render_with_configuration_and_check_model_presence(
+ config=(
+ {
+ 'app': 'app1',
+ 'models': (
+ 'app1.App1Model1',
+ 'app2.App2Model2',
+ )
+ },
+ ),
+ models_to_expect=[App1Model1, App2Model2],
+ models_to_be_absent=[App1Model2, App2Model1],
+ )
diff --git a/tests/test_models.py b/tests/test_models.py
deleted file mode 100644
index a379a89..0000000
--- a/tests/test_models.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-test_django-modeladmin-reorder
-------------
-
-Tests for `django-modeladmin-reorder` models module.
-"""
-
-import os
-import shutil
-import unittest
-
-from admin_reorder import models
-
-
-class Testadmin_reorder(unittest.TestCase):
-
- def setUp(self):
- pass
-
- def test_something(self):
- pass
-
- def tearDown(self):
- pass
diff --git a/tests/urls.py b/tests/urls.py
new file mode 100644
index 0000000..429c48f
--- /dev/null
+++ b/tests/urls.py
@@ -0,0 +1,14 @@
+from django.conf.urls import url
+from django.contrib import admin
+from django.http import HttpResponse
+
+
+def placeholder_view(request):
+ return HttpResponse('')
+
+
+urlpatterns = [
+ # Don't use path() yet for backwards compatibility in the test suite
+ url('^$', placeholder_view),
+ url('^admin/', admin.site.urls),
+]
diff --git a/tox.ini b/tox.ini
index 8ba9665..bf83d7d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,6 +4,6 @@ envlist = py26, py27, py33
[testenv]
setenv =
PYTHONPATH = {toxinidir}:{toxinidir}/admin_reorder
-commands = python runtests.py
+commands = pytest
deps =
- -r{toxinidir}/requirements-test.txt
\ No newline at end of file
+ -r{toxinidir}/requirements-test.txt