From 3f6d3e34de3d522d963e583ad2ebba777b1ad459 Mon Sep 17 00:00:00 2001 From: Fabre Florian Date: Tue, 4 Feb 2020 21:02:34 +0100 Subject: [PATCH 1/2] Refactor tox & travis scripts. Add lint target. Cleanup compatibility code for older django versions. --- .coveragerc | 6 +++ .travis.yml | 30 ++++++-------- makefile => Makefile | 0 async_messages/__init__.py | 2 +- async_messages/messages.py | 2 + async_messages/middleware.py | 28 +++---------- setup.py | 6 +-- tests/settings.py | 70 ++++++++++++-------------------- tests/tests.py | 79 ++++++++++++++++++++++++++++-------- tests/urls.py | 16 ++------ tests/views.py | 3 +- tox.ini | 19 ++++++++- 12 files changed, 141 insertions(+), 120 deletions(-) create mode 100644 .coveragerc rename makefile => Makefile (100%) diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..175cd2a --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +source = async_messages +branch = 1 + +[report] +omit = *tests* diff --git a/.travis.yml b/.travis.yml index d68e44d..1bde7e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,24 @@ +dist: bionic language: python -sudo: required -dist: xenial python: - 2.7 - 3.6 - 3.7 + - 3.8 -env: - - DJANGO=1.11 - - DJANGO=2.1 - - DJANGO=2.2 +stages: + - lint + - tests -matrix: - exclude: - - python: 2.7 - env: DJANGO=2.1 - - python: 2.7 - env: DJANGO=2.2 - - python: 3.7 - env: DJANGO=1.11 +jobs: + include: + - stage: lint + python: 3.6 + env: TOXENV=lint install: - - pip install Django==$DJANGO - - python setup.py develop + - pip install tox + - pip install -q tox-travis script: - - python manage.py test tests + - tox diff --git a/makefile b/Makefile similarity index 100% rename from makefile rename to Makefile diff --git a/async_messages/__init__.py b/async_messages/__init__.py index 2b6a62b..9391e75 100644 --- a/async_messages/__init__.py +++ b/async_messages/__init__.py @@ -1,5 +1,5 @@ -from django.core.cache import cache from django.contrib.messages import constants +from django.core.cache import cache class AsyncMessageException(Exception): diff --git a/async_messages/messages.py b/async_messages/messages.py index 35d8904..b498119 100644 --- a/async_messages/messages.py +++ b/async_messages/messages.py @@ -1,6 +1,8 @@ from django.contrib.messages import constants + from . import message_user + """ Mimic the django.contrib.messages API """ diff --git a/async_messages/middleware.py b/async_messages/middleware.py index e05a359..1e69693 100644 --- a/async_messages/middleware.py +++ b/async_messages/middleware.py @@ -1,30 +1,10 @@ -from distutils.version import StrictVersion - from django.contrib import messages - -try: - from django.utils.version import get_version -except ImportError: - from django import VERSION as DJANGO_VERSION - - def get_version(): - return ".".join(str(n) for n in DJANGO_VERSION[:3]) +from django.utils.deprecation import MiddlewareMixin from async_messages import get_messages -if StrictVersion(get_version()) >= StrictVersion("1.10.0"): - from django.utils.deprecation import MiddlewareMixin as _MiddlewareBase - - def _is_user_authenticated(user): - return bool(user.is_authenticated) -else: - _MiddlewareBase = object - def _is_user_authenticated(user): - return user.is_authenticated() - - -class AsyncMiddleware(_MiddlewareBase): +class AsyncMiddleware(MiddlewareMixin): # In Django>=1.10 User.is_authenticated is a property, not a method but a # strange one : CallbableBool. # - If default User is used you can use it as a boolean either a method. @@ -33,7 +13,7 @@ class AsyncMiddleware(_MiddlewareBase): def is_authenticated(self, request): if hasattr(request, "session") and hasattr(request, "user"): - return _is_user_authenticated(request.user) + return bool(request.user.is_authenticated) else: return False @@ -44,7 +24,9 @@ def process_response(self, request, response): """ if self.is_authenticated(request): msgs = get_messages(request.user) + if msgs: for msg, level in msgs: messages.add_message(request, level, msg) + return response diff --git a/setup.py b/setup.py index a6b8095..ed0be45 100755 --- a/setup.py +++ b/setup.py @@ -1,13 +1,13 @@ #!/usr/bin/env python -from setuptools import setup, find_packages +from setuptools import find_packages, setup setup(name='django-async-messages-redux', - version='0.4.1', + version='0.5.0', url='https://github.com/maurizi/django-async-messages', author='Michael Maurizi', author_email='michael@maurizi.org', description="Send asynchronous messages to users (eg from offline scripts). Useful for integration with Celery.", long_description=open('README.rst').read(), packages=find_packages(exclude=['tests']), - install_requires=['django>=1.4'], + install_requires=['django>=1.11'], ) diff --git a/tests/settings.py b/tests/settings.py index afec07f..81b8236 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,4 +1,3 @@ -import django from os import path DEBUG = False @@ -23,52 +22,37 @@ ] -if django.VERSION >= (1, 10): - MIDDLEWARE = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'async_messages.middleware.AsyncMiddleware' - ) -else: - MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'async_messages.middleware.AsyncMiddleware' - ) +MIDDLEWARE = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'async_messages.middleware.AsyncMiddleware' +) SITE_ID = 1 -if django.VERSION >= (1, 8): - TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - path.join(path.dirname(__file__), "templates"), +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ + path.join(path.dirname(__file__), "templates"), + ], + 'OPTIONS': { + 'context_processors': [ + # Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this + # list if you haven't customized them: + 'django.contrib.auth.context_processors.auth', + 'django.template.context_processors.debug', + 'django.template.context_processors.i18n', + 'django.template.context_processors.media', + 'django.template.context_processors.static', + 'django.template.context_processors.tz', + 'django.contrib.messages.context_processors.messages', ], - 'OPTIONS': { - 'context_processors': [ - # Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this - # list if you haven't customized them: - 'django.contrib.auth.context_processors.auth', - 'django.template.context_processors.debug', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - ], - }, }, - ] -else: - TEMPLATE_DIRS = ( - path.join(path.dirname(__file__), "templates"), - ) + }, +] SECRET_KEY = 'foobarbaz' diff --git a/tests/tests.py b/tests/tests.py index d5854bb..4e902cc 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -3,42 +3,69 @@ from django.contrib.messages import constants from django.test import TestCase from django.test.client import Client +from django.test.utils import override_settings +from django.utils.encoding import force_text -from async_messages import ( - message_user, message_users, messages, AsyncMessageException, -) +from async_messages import (AsyncMessageException, get_messages, message_user, + message_users, messages) class MiddlewareTests(TestCase): def setUp(self): - username, password = 'david', 'password' - self.user = User.objects.create_user(username, "django-async@test.com", password) + self.user = User.objects.create_user('david', "django-async@test.com", 'password') + self.userB = User.objects.create_user('johnathan', "django-async@test.com", 'password') + self.client = Client() - self.client.login(username=username, password=password) + self.client.login(username='david', password='password') + + self.clientB = Client() + self.clientB.login(username='johnathan', password='password') def test_message_appears_for_user(self): - message_user(self.user, "Hello") + message_user(self.user, "Hello david") + message_user(self.userB, "Hello john") + response = self.client.get('/') - msgs = list(response.context['messages']) - self.assertEqual(1, len((msgs))) - self.assertEqual('Hello', str((msgs)[0])) + self.assertEqual( + ['Hello david'], + [force_text(m) for m in response.context['messages']] + ) + + response = self.clientB.get('/') + self.assertEqual( + ['Hello john'], + [force_text(m) for m in response.context['messages']] + ) def test_message_appears_all_users(self): message_users(User.objects.all(), "Hello") + response = self.client.get('/') - msgs = list(response.context['messages']) - self.assertEqual(1, len((msgs))) - self.assertEqual('Hello', str((msgs)[0])) + self.assertEqual( + ['Hello'], + [force_text(m) for m in response.context['messages']] + ) + + response = self.clientB.get('/') + self.assertEqual( + ['Hello'], + [force_text(m) for m in response.context['messages']] + ) def test_message_queue(self): message_user(self.user, "First Message") message_user(self.user, "Second Message") + response = self.client.get('/') - msgs = list(response.context['messages']) - self.assertEqual(2, len((msgs))) - self.assertEqual('Second Message', str((msgs)[1])) + self.assertEqual( + ['First Message', 'Second Message'], + [force_text(m) for m in response.context['messages']] + ) + def test_message_queue_empty(self): + response = self.client.get('/') + self.assertEqual([], [force_text(m) for m in response.context['messages']]) class AnonynousUserTests(TestCase): def test_anonymous(self): @@ -54,9 +81,25 @@ def test_anonymous_message(self): with self.assertRaises(AsyncMessageException) as e: message_user(user, "Second Message") - self.assertEqual(str(e.exception), + self.assertEqual(force_text(e.exception), 'Anonymous users cannot send messages.') + def test_get_messages_anonymous(self): + client = Client() + user = auth.get_user(client) + + self.assertIsNone(user.id) + self.assertIsNone(get_messages(user)) + + @override_settings(MIDDLEWARE=[ + 'django.middleware.common.CommonMiddleware', + 'async_messages.middleware.AsyncMiddleware' + ]) + def test_no_session(self): + client = Client() + response = client.get('/') + self.assertEqual([], [force_text(m) for m in response.context['messages']]) + class TestMessagesApi(TestCase): def setUp(self): @@ -69,7 +112,7 @@ def assertMessageOk(self, level): response = self.client.get('/') msgs = list(response.context['messages']) self.assertEqual(1, len((msgs))) - self.assertEqual('Hello', str((msgs)[0])) + self.assertEqual('Hello', force_text((msgs)[0])) def test_info(self): messages.info(self.user, "Hello") diff --git a/tests/urls.py b/tests/urls.py index e853085..79781bc 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,15 +1,7 @@ -import django from django.conf.urls import url -if django.VERSION >= (1, 8): - from . import views +from . import views - urlpatterns = [ - url(r'^$', views.index, name='views_test'), - ] -else: - from django.conf.urls import patterns - - urlpatterns = patterns('', - url(r'^$', 'tests.views.index', name='views_test'), - ) +urlpatterns = [ + url(r'^$', views.index, name='views_test'), +] diff --git a/tests/views.py b/tests/views.py index 418fa4c..e122a85 100644 --- a/tests/views.py +++ b/tests/views.py @@ -1,5 +1,6 @@ -from django.template import Template, RequestContext from django.http import HttpResponse +from django.template import RequestContext, Template + def index(request): return HttpResponse(Template("").render(RequestContext(request, {'a': 1000}))) diff --git a/tox.ini b/tox.ini index 8e9581d..2d9e140 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,32 @@ [tox] -envlist = py27-dj{111},py36-dj{111,21,22} +envlist = py{27,36}-dj111 + py{36,37,38}-dj{21,22,30} + lint [testenv] deps = + coverage -r{toxinidir}/requirements.txt dj111: Django>=1.11,<2.0 dj21: Django>=2.1,<2.2 dj22: Django>=2.2,<2.3 + dj30: Django>=3.0,<3.1 commands = + python setup.py develop pip install -e . - python -Wd manage.py test tests + python -Wd {envbindir}/coverage run --branch --source=async_messages/ manage.py test {posargs:tests} + coverage report -m passenv = PYTHONPATH usedevelop = True whitelist_externals= pip + +[testenv:lint] +basepython = python3.6 +deps = + flake8 + isort +commands = + flake8 async_messages + isort --check-only -df -rc async_messages tests From 9e12ed09ba100a1d819178efb33592a4521114b4 Mon Sep 17 00:00:00 2001 From: Fabre Florian Date: Tue, 4 Feb 2020 21:22:03 +0100 Subject: [PATCH 2/2] Update readme & setup --- README.rst | 8 ++++++++ requirements.txt | 4 ++-- setup.py | 47 +++++++++++++++++++++++++++++++++++++---------- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 3a7e811..5de93c1 100644 --- a/README.rst +++ b/README.rst @@ -125,8 +125,16 @@ MIT_ Changelog ========= +0.5.0 +----- + +* Support for Django 2.2 & 3.0 +* Code cleanup +* Add linting & coverage + 0.4.1 ----- + * Improvements to Django 2+ and Python 3+ support 0.4.0 diff --git a/requirements.txt b/requirements.txt index 02bf3a6..ebbc053 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -django-nose>=1.1 -nose>=1.1.2 +django-nose>=1.2.1 +nose>=1.3.6 pinocchio>=0.3.1 diff --git a/setup.py b/setup.py index ed0be45..834441d 100755 --- a/setup.py +++ b/setup.py @@ -1,13 +1,40 @@ #!/usr/bin/env python from setuptools import find_packages, setup -setup(name='django-async-messages-redux', - version='0.5.0', - url='https://github.com/maurizi/django-async-messages', - author='Michael Maurizi', - author_email='michael@maurizi.org', - description="Send asynchronous messages to users (eg from offline scripts). Useful for integration with Celery.", - long_description=open('README.rst').read(), - packages=find_packages(exclude=['tests']), - install_requires=['django>=1.11'], - ) + +def readfile(path): + with open(path) as fd: + return fd.read() + + +setup( + name='django-async-messages-redux', + version='0.5.0', + url='https://github.com/maurizi/django-async-messages', + author='Michael Maurizi', + author_email='michael@maurizi.org', + description="Send asynchronous messages to users (eg from offline scripts). Useful for integration with Celery.", + long_description=readfile('README.rst'), + packages=find_packages(exclude=['tests']), + install_requires=[ + 'django>=1.11,<3.1' + ], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Web Environment', + 'Framework :: Django', + 'Framework :: Django :: 1.11', + 'Framework :: Django :: 2.2', + 'Framework :: Django :: 3.0', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Topic :: Utilities', + ], +)