Drop support Django < 2.2 and add support for Django > 3

This commit is contained in:
Hasan Ramezani 2020-11-23 18:43:50 +01:00
parent 5e6e52fcbb
commit cc06ab33fd
12 changed files with 42 additions and 163 deletions

View file

@ -2,7 +2,6 @@ language: python
dist: xenial dist: xenial
cache: pip cache: pip
python: python:
- '3.5'
- '3.6' - '3.6'
- '3.7' - '3.7'
- '3.8' - '3.8'

View file

@ -10,6 +10,10 @@ django-defender
:alt: Supported Python versions :alt: Supported Python versions
:target: https://pypi.org/project/django-defender/ :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 .. image:: https://travis-ci.org/jazzband/django-defender.svg
:target: https://travis-ci.org/jazzband/django-defender :target: https://travis-ci.org/jazzband/django-defender
:alt: Build Status :alt: Build Status
@ -104,8 +108,8 @@ Admin pages
Requirements Requirements
------------ ------------
* Python: 3.5, 3.6, 3.7, 3.8, PyPy * Python: 3.6, 3.7, 3.8, PyPy
* Django: 1.11, 2.1, 2.2, 3.x * Django: 2.2, 3.x
* Redis * Redis
@ -165,8 +169,8 @@ following to your ``urls.py``
.. code-block:: python .. code-block:: python
urlpatterns = [ urlpatterns = [
url(r'^admin/', include(admin.site.urls)), # normal admin path('admin/', include(admin.site.urls)), # normal admin
url(r'^admin/defender/', include('defender.urls')), # defender admin path('admin/defender/', include('defender.urls')), # defender admin
# your own patterns follow... # your own patterns follow...
] ]
@ -674,13 +678,13 @@ like:
.. code-block:: bash .. 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: With Code coverage:
.. code-block:: bash .. 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 Releasing

View file

@ -1,33 +1,27 @@
try: from django.contrib.auth.views import LoginView
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.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from .decorators import watch_login from .decorators import watch_login
class FailedLoginMiddleware(MIDDLEWARE_BASE_CLASS): class FailedLoginMiddleware:
""" Failed login middleware """ """ Failed login middleware """
patched = False patched = False
def __init__(self, *args, **kwargs): def __init__(self, get_response):
super().__init__(*args, **kwargs) self.get_response = get_response
# Watch the auth login. # Watch the auth login.
# Monkey-patch only once - otherwise we would be recording # Monkey-patch only once - otherwise we would be recording
# failed attempts multiple times! # failed attempts multiple times!
if not FailedLoginMiddleware.patched: if not FailedLoginMiddleware.patched:
# Django 1.11 turned the `login` function view into the our_decorator = watch_login()
# `LoginView` class-based view watch_login_method = method_decorator(our_decorator)
try: LoginView.dispatch = watch_login_method(LoginView.dispatch)
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)
FailedLoginMiddleware.patched = True FailedLoginMiddleware.patched = True
def __call__(self, request):
response = self.get_response(request)
return response

View file

@ -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"]

View file

@ -1,6 +1,6 @@
from django.conf.urls import url, include from django.urls import path
from django.contrib import admin from django.contrib import admin
from .urls import urlpatterns as original_urlpatterns from .urls import urlpatterns as original_urlpatterns
urlpatterns = [url(r"^admin/", admin.site.urls),] + original_urlpatterns urlpatterns = [path("admin/", admin.site.urls),] + original_urlpatterns

View file

@ -1,21 +1,15 @@
import random import random
import string import string
import time import time
from distutils.version import StrictVersion
from unittest.mock import patch from unittest.mock import patch
from django import get_version
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.backends.db import SessionStore from django.contrib.sessions.backends.db import SessionStore
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.test.client import RequestFactory from django.test.client import RequestFactory
from redis.client import Redis from redis.client import Redis
from django.urls import reverse
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
from . import utils from . import utils
from . import config from . import config
@ -33,8 +27,6 @@ from .test import DefenderTestCase, DefenderTransactionTestCase
LOGIN_FORM_KEY = '<form action="/admin/login/" method="post" id="login-form">' LOGIN_FORM_KEY = '<form action="/admin/login/" method="post" id="login-form">'
ADMIN_LOGIN_URL = reverse("admin:login") ADMIN_LOGIN_URL = reverse("admin:login")
DJANGO_VERSION = StrictVersion(get_version())
VALID_USERNAME = VALID_PASSWORD = "valid" VALID_USERNAME = VALID_PASSWORD = "valid"
UPPER_USERNAME = "VALID" UPPER_USERNAME = "VALID"
@ -400,12 +392,7 @@ class AccessAttemptTest(DefenderTestCase):
# Check if we are in the same login page # Check if we are in the same login page
self.assertContains(response, LOGIN_FORM_KEY) self.assertContains(response, LOGIN_FORM_KEY)
# RFC 7231 allows relative URIs in Location header. lockout_url = "/o/login/"
# 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/"
# So, we shouldn't have gotten a lock-out yet. # So, we shouldn't have gotten a lock-out yet.
# But we should get one now, check redirect make sure it is valid. # But we should get one now, check redirect make sure it is valid.

View file

@ -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 from .views import block_view, unblock_ip_view, unblock_username_view
urlpatterns = [ urlpatterns = [
url(r"^blocks/$", block_view, name="defender_blocks_view"), path("blocks/", block_view, name="defender_blocks_view"),
url( re_path(
r"^blocks/ip/(?P<ip_address>[A-Za-z0-9-._]+)/unblock$", "blocks/ip/(?P<ip_address>[A-Za-z0-9-._]+)/unblock",
unblock_ip_view, unblock_ip_view,
name="defender_unblock_ip_view", name="defender_unblock_ip_view",
), ),
url( path(
r"^blocks/username/(?P<username>[\w]+[^\/]*)/unblock$", "blocks/username/<path:username>/unblock",
unblock_username_view, unblock_username_view,
name="defender_unblock_username_view", name="defender_unblock_username_view",
), ),

View file

@ -1,11 +1,8 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.contrib.admin.views.decorators import staff_member_required 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 from .utils import get_blocked_ips, get_blocked_usernames, unblock_ip, unblock_username

View file

@ -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 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
``` ```

View file

@ -31,6 +31,9 @@ setup(
classifiers=[ classifiers=[
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Framework :: Django", "Framework :: Django",
"Framework :: Django :: 2.2",
"Framework :: Django :: 3.0",
"Framework :: Django :: 3.1",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent", "Operating System :: OS Independent",

13
tox.ini
View file

@ -1,16 +1,11 @@
[tox] [tox]
envlist = envlist =
# list of supported Django/Python versioons: # list of supported Django/Python versioons:
# https://docs.djangoproject.com/en/2.2/faq/install/#what-python-version-can-i-use-with-django py{36,37,38,py3}-django{22,30,31,master}
py{35,36,37,py3}-django111
py35-django(21,22)
py{36,37,py3}-django{21,22,master}
py38-django22
py38-{lint,docs} py38-{lint,docs}
[travis] [travis]
python = python =
3.5: py35
3.6: py36 3.6: py36
3.7: py37 3.7: py37
3.8: py38 3.8: py38
@ -19,13 +14,13 @@ python =
[testenv] [testenv]
deps = deps =
-rrequirements.txt -rrequirements.txt
django111: django>=1.11,<2.0
django21: django>=2.1,<2.2
django22: django>=2.2,<2.3 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 djangomaster: https://github.com/django/django/archive/master.tar.gz
usedevelop = True usedevelop = True
commands = 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 {envbindir}/coverage report -m
[testenv:py38-docs] [testenv:py38-docs]