diff --git a/.travis.yml b/.travis.yml index 5b96c1e..73ddc91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,30 +7,21 @@ matrix: env: TOXENV=docs - python: "2.7" env: TOXENV=flake8 - - python: "2.7" - env: TOXENV=py27-1.8 - - python: "3.5" - env: TOXENV=py35-1.8 - - python: "2.7" - env: TOXENV=py27-1.9 - - python: "3.5" - env: TOXENV=py35-1.9 - - python: "2.7" - env: TOXENV=py27-1.10 - - python: "3.5" - env: TOXENV=py35-1.10 - python: "2.7" env: TOXENV=py27-1.11 + - python: "3.4" + env: TOXENV=py34-1.11 + - python: "3.5" + env: TOXENV=py35-2.1 - python: "3.6" - env: TOXENV=py36-1.11 - - python: "3.6" - env: TOXENV=py36-2.0 - - python: "3.6" - env: TOXENV=py36-master + env: TOXENV=py36-2.2 + dist: xenial # For SQLite 3.8.3 or later + - python: "3.7" + env: TOXENV=py37-master + dist: xenial # For Python 3.7 allow_failures: - - env: TOXENV=py36-master + - env: TOXENV=py37-master install: - - pip install coveralls tox + - pip install tox script: - tox -after_success: coveralls diff --git a/Makefile b/Makefile index 0a479db..b69aecd 100644 --- a/Makefile +++ b/Makefile @@ -70,4 +70,4 @@ test-release: clean sdist twine upload --repository test dist/* python -m webbrowser -n https://testpypi.python.org/pypi/django-tidings -.PHONY: help clean coverage coveragehtml develop lint qa qa-all release sdist test test-all test-release +.PHONY: help clean coverage coveragehtml develop docs lint qa qa-all release sdist test test-all test-release diff --git a/docs/requirements.txt b/docs/requirements.txt index 4448ab6..8726cec 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ -r ../tests/requirements.txt -Sphinx==1.6.7 -sphinx-rtd-theme==0.2.4 -Django<2.1 +Sphinx==1.8.5 +sphinx-rtd-theme==0.4.3 +Django<2.2 diff --git a/requirements.dev.txt b/requirements.dev.txt index 48df92f..6c9c951 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -1,6 +1,6 @@ # Testing and development requirements -r tests/requirements.txt -Django<2.1 +Django<2.2 check-manifest coverage flake8 diff --git a/setup.py b/setup.py index 6bf0875..4b47cdb 100644 --- a/setup.py +++ b/setup.py @@ -49,11 +49,10 @@ def long_description(): classifiers=[ 'Environment :: Web Environment', 'Framework :: Django', - 'Framework :: Django :: 1.8', - 'Framework :: Django :: 1.9', - 'Framework :: Django :: 1.10', 'Framework :: Django :: 1.11', 'Framework :: Django :: 2.0', + 'Framework :: Django :: 2.1', + 'Framework :: Django :: 2.2', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', @@ -65,6 +64,7 @@ def long_description(): 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Communications :: Email', 'Topic :: Software Development :: Libraries :: Python Modules'], ) diff --git a/tests/base.py b/tests/base.py index c7cbcf8..d95282b 100644 --- a/tests/base.py +++ b/tests/base.py @@ -2,8 +2,8 @@ from string import ascii_letters from django.contrib.auth import get_user_model -from django.utils.six.moves import range +from tidings.compat import range from tidings.models import Watch, WatchFilter diff --git a/tests/test_events.py b/tests/test_events.py index e5a8cce..e063d27 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -4,8 +4,8 @@ from django.core import mail from django.core.mail import EmailMessage from django.test import TestCase, override_settings -from django.utils.six.moves import range +from tidings.compat import range from tidings.events import Event, _unique_by_email, EventUnion, InstanceEvent from tidings.models import Watch, EmailUser @@ -42,6 +42,16 @@ class FilteredContentTypeEvent(ContentTypeEvent): filters = set(['color', 'flavor']) +fire_simple_event_called = False + + +class FireSimpleEvent(SimpleEvent): + def _mails(self, users_and_watches): + global fire_simple_event_called + fire_simple_event_called = True + return [] + + class UsersWatchingTests(TestCase): """Unit tests for Event._users_watching_by_filter()""" @@ -262,19 +272,13 @@ def _users_watching(self): def test_fire(self): """Assert firing the union gets the mails from the first event.""" - - class FireSimpleEvent(SimpleEvent): - called = False - - def _mails(self, users_and_watches): - self.called = True - return [] - + global fire_simple_event_called watch(event_type=TYPE, email='he@llo.com').save() simple_event = FireSimpleEvent() + fire_simple_event_called = False another_event = AnotherEvent() EventUnion(simple_event, another_event).fire() - self.assertTrue(simple_event.called) + self.assertTrue(fire_simple_event_called) def test_watch_lists(self): """Ensure the Union returns every watch a user has.""" diff --git a/tests/test_utils.py b/tests/test_utils.py index 5d7b075..3304a8b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,7 +1,7 @@ from django.core.exceptions import ImproperlyConfigured from django.test import override_settings, TestCase -from django.utils.six.moves import range, reduce +from tidings.compat import range, reduce from tidings.utils import collate, import_from_setting diff --git a/tidings/compat.py b/tidings/compat.py index 3d23679..a65033a 100644 --- a/tidings/compat.py +++ b/tidings/compat.py @@ -1,21 +1,28 @@ """Compatibility functions for tidings.""" -# The standard Django reverse function -# This is the default value if TIDINGS_REVERSE is not set +# Python 2/3 compatibility for Django 1.11 try: - # Django 1.10 and later - from django.urls import reverse + # Django 2.2 and earlier include six, but it is only + # needed for Python 2.7 under Django 1.11. + from django.utils.six import (iteritems, iterkeys, string_types, next, + text_type) + from django.utils.six.moves import range, reduce except ImportError: - # Django 1.9 and earlier - from django.core.urlresolvers import reverse -assert reverse + # Django 3.0 drops the six library, but only runs under Python 3 + # Copy Python 3 variants from https://github.com/benjaminp/six + from functools import reduce + assert reduce # Make flake8 happier -def is_authenticated(user): - """Is a user instance authenticated?""" - if callable(user.is_authenticated): - # Django 1.9 and earlier - return user.is_authenticated() - else: - # Django 1.10 and later - return user.is_authenticated + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + string_types = str, + text_type = str + + # Asign built-ins to importable variables + next = next + range = range diff --git a/tidings/events.py b/tidings/events.py index fcbfa71..5609847 100644 --- a/tidings/events.py +++ b/tidings/events.py @@ -7,12 +7,10 @@ from django.contrib.contenttypes.models import ContentType from django.core import mail from django.db.models import Q -from django.utils.six import iteritems, iterkeys, string_types -from django.utils.six.moves import range from celery.task import task -from .compat import is_authenticated +from .compat import iteritems, iterkeys, string_types, range from .models import Watch, WatchFilter, EmailUser, multi_raw from .utils import collate, hash_to_unsigned @@ -70,8 +68,8 @@ def ensure_user_has_email(user, cluster_email): watches) favorite_user, watches = u, [] cluster_email = row_email - elif ((not favorite_user.email or not is_authenticated(u)) and - u.email and is_authenticated(u)): + elif ((not favorite_user.email or not u.is_authenticated) and + u.email and u.is_authenticated): favorite_user = u watches.extend(w) if favorite_user is not None: @@ -310,7 +308,7 @@ def _watches_belonging_to_user(cls, user_or_email, object_id=None, if isinstance(user_or_email, string_types): user_condition = Q(email=user_or_email) - elif is_authenticated(user_or_email): + elif user_or_email.is_authenticated: user_condition = Q(user=user_or_email) else: return Watch.objects.none() diff --git a/tidings/models.py b/tidings/models.py index 4326a24..2ce2809 100644 --- a/tidings/models.py +++ b/tidings/models.py @@ -5,8 +5,8 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site from django.db import models, connections, router -from django.utils.six import next, text_type +from .compat import next, text_type from .utils import import_from_setting, reverse @@ -130,9 +130,7 @@ class EmailUser(AnonymousUser): """An anonymous user identified only by email address. This is based on Django's AnonymousUser, so you can use the - ``is_authenticated`` property (Django 1.10 or later) or - ``is_authenticated`` method (Django 1.9 or earlier) to tell that - this is an anonymous user. + ``is_authenticated`` property to tell that this is an anonymous user. """ def __init__(self, email=''): self.email = email diff --git a/tidings/utils.py b/tidings/utils.py index e3f200a..ba17325 100644 --- a/tidings/utils.py +++ b/tidings/utils.py @@ -4,10 +4,10 @@ from django.core.exceptions import ImproperlyConfigured from django.core.mail import EmailMessage from django.template import Context, loader +from django.urls import reverse as django_reverse from django.utils.module_loading import import_string -from django.utils.six import next, string_types -from .compat import reverse as django_reverse +from .compat import next, string_types def collate(*iterables, **kwargs): diff --git a/tox.ini b/tox.ini index e0638ae..7196964 100644 --- a/tox.ini +++ b/tox.ini @@ -4,28 +4,30 @@ skip_missing_interpreters = true envlist = docs, flake8, - py{27,34,35}-1.8 - py{27,34,35,36}-{1.9,1.10,1.11} - py{35,36}-{2.0,master} + py{27,34,35,36}-1.11 + py{35,36,37}-{2.0,2.1,2.2,master} [testenv] +passenv = TRAVIS TRAVIS_* basepython = py27: python2.7 py34: python3.4 py35: python3.5 py36: python3.6 + py37: python3.7 usedevelop = true pip_pre = true -commands = make coverage +commands = + make coverage + coveralls deps = - coverage - 1.8: Django>=1.8,<1.9 - 1.9: Django>=1.9,<1.10 - 1.10: Django>=1.10,<1.11 + coveralls 1.11: Django>=1.11,<2.0 2.0: Django>=2.0,<2.1 + 2.1: Django>=2.1,<2.2 + 2.2: Django>=2.2a1,<2.3 master: https://github.com/django/django/archive/master.tar.gz - {1.8,1.9,1.10,1.11,2.0}: -r{toxinidir}/tests/requirements.txt + {1.11,2.0,2.1,2.2}: -r{toxinidir}/tests/requirements.txt master: -r{toxinidir}/tests/requirements-latest.txt whitelist_externals = make @@ -38,5 +40,5 @@ commands = make docs basepython = python2.7 deps = flake8 - Django<1.9 + Django<2.2 commands = make lint