From cc06ab33fd2b71059e78776d29a1254a83da95d7 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Mon, 23 Nov 2020 18:43:50 +0100 Subject: [PATCH] Drop support Django < 2.2 and add support for Django > 3 --- .travis.yml | 1 - README.rst | 16 ++-- defender/middleware.py | 30 +++---- defender/south_migrations/0001_initial.py | 100 ---------------------- defender/south_migrations/__init__.py | 0 defender/test_urls.py | 4 +- defender/tests.py | 17 +--- defender/urls.py | 12 +-- defender/views.py | 5 +- exampleapp/readme.md | 4 +- setup.py | 3 + tox.ini | 13 +-- 12 files changed, 42 insertions(+), 163 deletions(-) delete mode 100644 defender/south_migrations/0001_initial.py delete mode 100644 defender/south_migrations/__init__.py diff --git a/.travis.yml b/.travis.yml index 7d513b8..c0fcbbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python dist: xenial cache: pip python: -- '3.5' - '3.6' - '3.7' - '3.8' diff --git a/README.rst b/README.rst index 7921768..01b956f 100644 --- a/README.rst +++ b/README.rst @@ -10,6 +10,10 @@ django-defender :alt: Supported Python versions :target: https://pypi.org/project/django-defender/ +.. image:: https://img.shields.io/pypi/djversions/django-defender.svg + :target: https://pypi.org/project/django-defender/ + :alt: Supported Django versions + .. image:: https://travis-ci.org/jazzband/django-defender.svg :target: https://travis-ci.org/jazzband/django-defender :alt: Build Status @@ -104,8 +108,8 @@ Admin pages Requirements ------------ -* Python: 3.5, 3.6, 3.7, 3.8, PyPy -* Django: 1.11, 2.1, 2.2, 3.x +* Python: 3.6, 3.7, 3.8, PyPy +* Django: 2.2, 3.x * Redis @@ -165,8 +169,8 @@ following to your ``urls.py`` .. code-block:: python urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), # normal admin - url(r'^admin/defender/', include('defender.urls')), # defender admin + path('admin/', include(admin.site.urls)), # normal admin + path('admin/defender/', include('defender.urls')), # defender admin # your own patterns follow... ] @@ -674,13 +678,13 @@ like: .. code-block:: bash - PYTHONPATH=$PYTHONPATH:$PWD django-admin.py test defender --settings=defender.test_settings + PYTHONPATH=$PYTHONPATH:$PWD django-admin test defender --settings=defender.test_settings With Code coverage: .. code-block:: bash - PYTHONPATH=$PYTHONPATH:$PWD coverage run --source=defender $(which django-admin.py) test defender --settings=defender.test_settings + PYTHONPATH=$PYTHONPATH:$PWD coverage run --source=defender $(which django-admin) test defender --settings=defender.test_settings Releasing diff --git a/defender/middleware.py b/defender/middleware.py index 914c5af..5f244b0 100644 --- a/defender/middleware.py +++ b/defender/middleware.py @@ -1,33 +1,27 @@ -try: - from django.utils.deprecation import MiddlewareMixin as MIDDLEWARE_BASE_CLASS -except ImportError: - MIDDLEWARE_BASE_CLASS = object -from django.contrib.auth import views as auth_views +from django.contrib.auth.views import LoginView from django.utils.decorators import method_decorator from .decorators import watch_login -class FailedLoginMiddleware(MIDDLEWARE_BASE_CLASS): +class FailedLoginMiddleware: """ Failed login middleware """ patched = False - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, get_response): + self.get_response = get_response + # Watch the auth login. # Monkey-patch only once - otherwise we would be recording # failed attempts multiple times! if not FailedLoginMiddleware.patched: - # Django 1.11 turned the `login` function view into the - # `LoginView` class-based view - try: - from django.contrib.auth.views import LoginView - - our_decorator = watch_login() - watch_login_method = method_decorator(our_decorator) - LoginView.dispatch = watch_login_method(LoginView.dispatch) - except ImportError: # Django < 1.11 - auth_views.login = watch_login()(auth_views.login) + our_decorator = watch_login() + watch_login_method = method_decorator(our_decorator) + LoginView.dispatch = watch_login_method(LoginView.dispatch) FailedLoginMiddleware.patched = True + + def __call__(self, request): + response = self.get_response(request) + return response \ No newline at end of file diff --git a/defender/south_migrations/0001_initial.py b/defender/south_migrations/0001_initial.py deleted file mode 100644 index d491445..0000000 --- a/defender/south_migrations/0001_initial.py +++ /dev/null @@ -1,100 +0,0 @@ -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - """Initial Migration for Defender""" - - def forwards(self, orm): - """ Adding model 'AccessAttempt' """ - db.create_table( - "defender_accessattempt", - ( - ("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)), - ( - "user_agent", - self.gf("django.db.models.fields.CharField")(max_length=255), - ), - ( - "ip_address", - self.gf("django.db.models.fields.GenericIPAddressField")( - max_length=39, null=True - ), - ), - ( - "username", - self.gf("django.db.models.fields.CharField")( - max_length=255, null=True - ), - ), - ( - "http_accept", - self.gf("django.db.models.fields.CharField")(max_length=1025), - ), - ( - "path_info", - self.gf("django.db.models.fields.CharField")(max_length=255), - ), - ( - "attempt_time", - self.gf("django.db.models.fields.DateTimeField")( - auto_now_add=True, blank=True - ), - ), - ( - "login_valid", - self.gf("django.db.models.fields.BooleanField")(default=False), - ), - ), - ) - db.send_create_signal("defender", ["AccessAttempt"]) - - def backwards(self, orm): - # Deleting model 'AccessAttempt' - db.delete_table("defender_accessattempt") - - models = { - "defender.accessattempt": { - "Meta": {"ordering": "[u'-attempt_time']", "object_name": "AccessAttempt"}, - "attempt_time": ( - "django.db.models.fields.DateTimeField", - [], - {"auto_now_add": "True", "blank": "True"}, - ), - "http_accept": ( - "django.db.models.fields.CharField", - [], - {"max_length": "1025"}, - ), - "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}), - "ip_address": ( - "django.db.models.fields.GenericIPAddressField", - [], - {"max_length": "39", "null": "True"}, - ), - "login_valid": ( - "django.db.models.fields.BooleanField", - [], - {"default": "False"}, - ), - "path_info": ( - "django.db.models.fields.CharField", - [], - {"max_length": "255"}, - ), - "user_agent": ( - "django.db.models.fields.CharField", - [], - {"max_length": "255"}, - ), - "username": ( - "django.db.models.fields.CharField", - [], - {"max_length": "255", "null": "True"}, - ), - } - } - - complete_apps = ["defender"] diff --git a/defender/south_migrations/__init__.py b/defender/south_migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/defender/test_urls.py b/defender/test_urls.py index 1776d41..6c9135c 100644 --- a/defender/test_urls.py +++ b/defender/test_urls.py @@ -1,6 +1,6 @@ -from django.conf.urls import url, include +from django.urls import path from django.contrib import admin from .urls import urlpatterns as original_urlpatterns -urlpatterns = [url(r"^admin/", admin.site.urls),] + original_urlpatterns +urlpatterns = [path("admin/", admin.site.urls),] + original_urlpatterns diff --git a/defender/tests.py b/defender/tests.py index 99b4528..491df36 100644 --- a/defender/tests.py +++ b/defender/tests.py @@ -1,21 +1,15 @@ import random import string import time -from distutils.version import StrictVersion from unittest.mock import patch -from django import get_version from django.contrib.auth.models import User from django.contrib.auth.models import AnonymousUser from django.contrib.sessions.backends.db import SessionStore from django.http import HttpRequest, HttpResponse from django.test.client import RequestFactory from redis.client import Redis - -try: - from django.urls import reverse -except ImportError: - from django.core.urlresolvers import reverse +from django.urls import reverse from . import utils from . import config @@ -33,8 +27,6 @@ from .test import DefenderTestCase, DefenderTransactionTestCase LOGIN_FORM_KEY = '
' ADMIN_LOGIN_URL = reverse("admin:login") -DJANGO_VERSION = StrictVersion(get_version()) - VALID_USERNAME = VALID_PASSWORD = "valid" UPPER_USERNAME = "VALID" @@ -400,12 +392,7 @@ class AccessAttemptTest(DefenderTestCase): # Check if we are in the same login page self.assertContains(response, LOGIN_FORM_KEY) - # RFC 7231 allows relative URIs in Location header. - # Django from version 1.9 is support this: - # https://docs.djangoproject.com/en/1.9/releases/1.9/#http-redirects-no-longer-forced-to-absolute-uris - lockout_url = "http://testserver/o/login/" - if DJANGO_VERSION >= StrictVersion("1.9"): - lockout_url = "/o/login/" + lockout_url = "/o/login/" # So, we shouldn't have gotten a lock-out yet. # But we should get one now, check redirect make sure it is valid. diff --git a/defender/urls.py b/defender/urls.py index 174d5e8..6fe2b76 100644 --- a/defender/urls.py +++ b/defender/urls.py @@ -1,15 +1,15 @@ -from django.conf.urls import url +from django.urls import path, re_path from .views import block_view, unblock_ip_view, unblock_username_view urlpatterns = [ - url(r"^blocks/$", block_view, name="defender_blocks_view"), - url( - r"^blocks/ip/(?P[A-Za-z0-9-._]+)/unblock$", + path("blocks/", block_view, name="defender_blocks_view"), + re_path( + "blocks/ip/(?P[A-Za-z0-9-._]+)/unblock", unblock_ip_view, name="defender_unblock_ip_view", ), - url( - r"^blocks/username/(?P[\w]+[^\/]*)/unblock$", + path( + "blocks/username//unblock", unblock_username_view, name="defender_unblock_username_view", ), diff --git a/defender/views.py b/defender/views.py index 52585d8..6d78006 100644 --- a/defender/views.py +++ b/defender/views.py @@ -1,11 +1,8 @@ from django.shortcuts import render from django.http import HttpResponseRedirect from django.contrib.admin.views.decorators import staff_member_required +from django.urls import reverse -try: - from django.urls import reverse -except ImportError: - from django.core.urlresolvers import reverse from .utils import get_blocked_ips, get_blocked_usernames, unblock_ip, unblock_username diff --git a/exampleapp/readme.md b/exampleapp/readme.md index 3e1e2f4..97d465f 100644 --- a/exampleapp/readme.md +++ b/exampleapp/readme.md @@ -8,7 +8,7 @@ This is just a simple example app, used for testing and showing how things work ``` mkdir -p exampleapp/static exampleapp/media/static -PYTHONPATH=$PYTHONPATH:$PWD django-admin.py collectstatic --noinput --settings=exampleapp.settings +PYTHONPATH=$PYTHONPATH:$PWD django-admin collectstatic --noinput --settings=exampleapp.settings -PYTHONPATH=$PYTHONPATH:$PWD django-admin.py runserver --settings=exampleapp.settings +PYTHONPATH=$PYTHONPATH:$PWD django-admin runserver --settings=exampleapp.settings ``` diff --git a/setup.py b/setup.py index cba40bb..f3d55b7 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,9 @@ setup( classifiers=[ "Development Status :: 5 - Production/Stable", "Framework :: Django", + "Framework :: Django :: 2.2", + "Framework :: Django :: 3.0", + "Framework :: Django :: 3.1", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", diff --git a/tox.ini b/tox.ini index 0fceacb..1cc6fdf 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,11 @@ [tox] envlist = # list of supported Django/Python versioons: - # https://docs.djangoproject.com/en/2.2/faq/install/#what-python-version-can-i-use-with-django - py{35,36,37,py3}-django111 - py35-django(21,22) - py{36,37,py3}-django{21,22,master} - py38-django22 + py{36,37,38,py3}-django{22,30,31,master} py38-{lint,docs} [travis] python = - 3.5: py35 3.6: py36 3.7: py37 3.8: py38 @@ -19,13 +14,13 @@ python = [testenv] deps = -rrequirements.txt - django111: django>=1.11,<2.0 - django21: django>=2.1,<2.2 django22: django>=2.2,<2.3 + django30: django>=3.0,<3.1 + django31: django>=3.1,<3.2 djangomaster: https://github.com/django/django/archive/master.tar.gz usedevelop = True commands = - {envbindir}/coverage run --source=defender {envbindir}/django-admin.py test defender --settings=defender.travis_settings + {envbindir}/coverage run --source=defender {envbindir}/django-admin test defender --settings=defender.travis_settings {envbindir}/coverage report -m [testenv:py38-docs]