Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🌟 Add support for Django2.0 #121

Merged
merged 3 commits into from
Jul 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
language: python
sudo: false

python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- 2.7
- 3.4
- 3.5
- 3.6

env:
- DJANGO_VERSION=1.9.11
- DJANGO_VERSION=1.10.5
- DJANGO_VERSION=1.11.2
- DJANGO_VERSION=1.11.14
- DJANGO_VERSION=2.0.7

matrix:
exclude:
- python: 2.7
env: DJANGO_VERSION=2.0.7

install:
- pip install -q -rrequirements/base.txt
- pip install -q -rrequirements/tests.txt
- pip install -q -r requirements/base.txt
- pip install -q -r requirements/tests.txt
- pip install -q django==$DJANGO_VERSION
- pip install coveralls

before_script:
- createdb -E UTF-8 dash -U postgres -O $USER

script:
- coverage run manage.py test --settings=test_runner.settings_travis --verbosity=2 --noinput
- flake8

after_success:
- coveralls
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
2.0.0 (2018-07-16)
==================

* Add support for Django 2.0, drop support for Django 1.9 and Django 1.10

1.11.9 (2018-05-3)
===================
* Create SmartminTestMixin https://github.com/nyaruka/smartmin/pull/116
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Smartmin tries to stay in lock step with the latest Django versions. With each n
will be released and we will save the major changes (possibly breaking backwards compatibility) on these versions. This
includes updating to the latest version of Twitter Bootstrap.

The latest version is the 1.11.* series which works against Django 1.9, 1.10 and 1.11.
The latest version is the 2.0.* series which works against Django 1.11 and 2.0.

About
=====
Expand Down
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
celery
django>=1.9,<2.0
django>=1.11,<2.1
django_compressor
pytz
redis
Expand Down
2 changes: 1 addition & 1 deletion smartmin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from __future__ import unicode_literals

__version__ = '1.11.9'
__version__ = '2.0.0'
4 changes: 2 additions & 2 deletions smartmin/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def process_response(self, request, response):


class ProfileMiddleware(MiddlewareMixin):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MiddlewareMixin is no longer needed

def __init__(self):
def __init__(self, get_response=None):
pass

def process_view(self, request, view, *args, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This middleware should be updated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind

Expand Down Expand Up @@ -69,7 +69,7 @@ def process_view(self, request, view, *args, **kwargs):
return None


class TimezoneMiddleware(object):
class TimezoneMiddleware(MiddlewareMixin):
def process_request(self, request):
user_tz = getattr(settings, 'USER_TIME_ZONE', None)

Expand Down
4 changes: 2 additions & 2 deletions smartmin/templatetags/smartmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
from datetime import datetime, timedelta
from django import template
from django.conf import settings
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.template import TemplateSyntaxError
from django.utils import timezone

register = template.Library()


@register.assignment_tag
@register.simple_tag
def get_hostname():
if settings.HOSTNAME:
return settings.HOSTNAME
Expand Down
2 changes: 1 addition & 1 deletion smartmin/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.test.testcases import TestCase
from django.utils.encoding import force_str
from six.moves.urllib.parse import urlparse
Expand Down
8 changes: 5 additions & 3 deletions smartmin/users/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import django.views.static

from django.conf import settings
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.http import HttpResponseRedirect
from .models import PasswordHistory

Expand All @@ -19,14 +19,16 @@ class ChangePasswordMiddleware(MiddlewareMixin):
Redirects all users to the password change form if we find that a user's
password is expired.
"""
def __init__(self):
def __init__(self, get_response=None):
super(ChangePasswordMiddleware, self).__init__(get_response)

self.password_expire = getattr(settings, 'USER_PASSWORD_EXPIRATION', -1)

def process_view(self, request, view, *args, **kwargs):
newpassword_path = reverse('users.user_newpassword', args=[0])
logout_path = reverse('users.user_logout')

if (self.password_expire < 0 or not request.user.is_authenticated() or view == django.views.static.serve or request.path == newpassword_path or request.path == logout_path): # noqa
if (self.password_expire < 0 or not request.user.is_authenticated or view == django.views.static.serve or request.path == newpassword_path or request.path == logout_path): # noqa
return

if PasswordHistory.is_password_expired(request.user):
Expand Down
6 changes: 3 additions & 3 deletions smartmin/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from django.contrib.auth.models import Group
from django.contrib.auth.views import login as django_login
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.http import HttpResponseRedirect
from django.template import loader
from django.utils import timezone
Expand Down Expand Up @@ -338,7 +338,7 @@ def get_context_data(self, *args, **kwargs):
return context_data

def has_permission(self, request, *args, **kwargs):
return request.user.is_authenticated()
return request.user.is_authenticated

def get_object(self, queryset=None):
return self.request.user
Expand Down Expand Up @@ -469,4 +469,4 @@ def login(request, template_name='smartmin/users/login.html',
return django_login(request, template_name='smartmin/users/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm,
current_app=None, extra_context=dict(allow_email_recovery=allow_email_recovery))
extra_context=dict(allow_email_recovery=allow_email_recovery))
2 changes: 1 addition & 1 deletion smartmin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.contrib import messages
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.db import IntegrityError
from django.db.models import Q
from django.http import HttpResponseRedirect, HttpResponse, JsonResponse
Expand Down
8 changes: 4 additions & 4 deletions test_runner/blog/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class Migration(migrations.Migration):
('created_on', models.DateTimeField(help_text='When this item was originally created', auto_now_add=True)),
('modified_on', models.DateTimeField(help_text='When this item was last modified', auto_now=True)),
('name', models.SlugField(help_text='The name of this category', unique=True, max_length=64)),
('created_by', models.ForeignKey(related_name='blog_category_creations', to=settings.AUTH_USER_MODEL, help_text='The user which originally created this item')),
('modified_by', models.ForeignKey(related_name='blog_category_modifications', to=settings.AUTH_USER_MODEL, help_text='The user which last modified this item')),
('created_by', models.ForeignKey(related_name='blog_category_creations', to=settings.AUTH_USER_MODEL, on_delete=models.PROTECT, help_text='The user which originally created this item')),
('modified_by', models.ForeignKey(related_name='blog_category_modifications', to=settings.AUTH_USER_MODEL, on_delete=models.PROTECT, help_text='The user which last modified this item')),
],
options={
'abstract': False,
Expand All @@ -38,8 +38,8 @@ class Migration(migrations.Migration):
('body', models.TextField(help_text='The body of the post, go crazy')),
('order', models.IntegerField(help_text='The order for this post, posts with smaller orders come first')),
('tags', models.CharField(help_text='Any tags for this post', max_length=128)),
('created_by', models.ForeignKey(related_name='blog_post_creations', to=settings.AUTH_USER_MODEL, help_text='The user which originally created this item')),
('modified_by', models.ForeignKey(related_name='blog_post_modifications', to=settings.AUTH_USER_MODEL, help_text='The user which last modified this item')),
('created_by', models.ForeignKey(related_name='blog_post_creations', to=settings.AUTH_USER_MODEL, on_delete=models.PROTECT, help_text='The user which originally created this item')),
('modified_by', models.ForeignKey(related_name='blog_post_modifications', to=settings.AUTH_USER_MODEL, on_delete=models.PROTECT, help_text='The user which last modified this item')),
],
options={
'abstract': False,
Expand Down
14 changes: 7 additions & 7 deletions test_runner/blog/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.contrib.auth.models import User, Group
from django.core import mail
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.test import TestCase
from django.test.client import Client
from django.test.utils import override_settings
Expand Down Expand Up @@ -904,7 +904,7 @@ def test_lockout(self):
# try to log in four times
for i in range(4):
response = self.client.post(login_url, post_data)
self.assertFalse(response.context['user'].is_authenticated())
self.assertFalse(response.context['user'].is_authenticated)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this was a method in Django 1.x but a field in Django 2.x? Problem with this test is that if is_authenticated is a method.. then is always true.. I don't get how this is working...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertFalse is a simple truthy check if cond: ... and a is_authenticated method is not 'undefined' so it's always 'true'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I just poked around the Django src and it seems is_authenticated has been a property for a while - but it returns a magical CallableFalse which can also be called. Makes sense now.


# on the fifth failed login we get redirected
response = self.client.post(login_url, post_data)
Expand Down Expand Up @@ -1050,14 +1050,14 @@ def doLockout(self):

# on the fifth time it should fail
response = self.client.post(reverse('users.user_login'), post_data, follow=True)
self.assertFalse(response.context['user'].is_authenticated())
self.assertFalse(response.context['user'].is_authenticated)
content = response.content.decode("utf-8")
self.assertEqual(content.find(reverse('users.user_forget')), -1)

# even with right password, no dice
post_data = dict(username='plain', password='plain')
response = self.client.post(reverse('users.user_login'), post_data, follow=True)
self.assertFalse(response.context['user'].is_authenticated())
self.assertFalse(response.context['user'].is_authenticated)
content = response.content.decode("utf-8")
self.assertEqual(content.find(reverse('users.user_forget')), -1)

Expand All @@ -1079,7 +1079,7 @@ def testNoRecovery(self):

# should now be able to log in
response = self.client.post(reverse('users.user_login'), post_data, follow=True)
self.assertTrue(response.context['user'].is_authenticated())
self.assertTrue(response.context['user'].is_authenticated)

def testNoRecoveryNoTimeout(self):
with self.settings(USER_ALLOW_EMAIL_RECOVERY=False, USER_LOCKOUT_TIMEOUT=-1):
Expand Down Expand Up @@ -1124,7 +1124,7 @@ def testNoRecoveryNoTimeout(self):

post_data = dict(username='plain', password='Password1')
response = self.client.post(reverse('users.user_login'), post_data, follow=True)
self.assertTrue(response.context['user'].is_authenticated())
self.assertTrue(response.context['user'].is_authenticated)


class PasswordExpirationTestCase(TestCase):
Expand All @@ -1146,7 +1146,7 @@ def testNoExpiration(self):
self.client.logout()
post_data = dict(username='plain', password='Password1 ')
response = self.client.post(reverse('users.user_login'), post_data, follow=True)
self.assertTrue(response.context['user'].is_authenticated())
self.assertTrue(response.context['user'].is_authenticated)

# we shouldn't be on a page asking us for a new password
self.assertFalse('form' in response.context)
Expand Down
2 changes: 1 addition & 1 deletion test_runner/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'w4*mtn&nquc57h@$-05gva+2ucq0$tnczy#!d=t4%1&pl!p=jo'

MIDDLEWARE_CLASSES = (
MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
Expand Down
2 changes: 1 addition & 1 deletion test_runner/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
url(r'^csv_imports/', include('smartmin.csv_imports.urls')),

# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
url(r'^admin/', admin.site.urls),
]