Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #22 from masci/py3_django15_support

Support for Python3 and Django1.5+, added tests
  • Loading branch information...
commit 3116c616409c47109e5bb1a3fbf273d380b5c2ea 2 parents 8d63cfe + 23f0afe
Patrick Altman paltman authored
36 .gitignore
View
@@ -0,0 +1,36 @@
+*.py[cod]
+
+# C extensions
+*.so
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+__pycache__
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
1  .pylintrc
View
@@ -1,5 +1,6 @@
[MASTER]
load-plugins=pinax.checkers.style
+ignore:compat.py
[MESSAGES CONTROL]
# Pointless whinging
6 .travis.yml
View
@@ -2,8 +2,8 @@ language: python
python:
- "2.7"
env:
- - DJANGO=1.4
- - DJANGO=1.5
+ - DJANGO=1.4.8
+ - DJANGO=1.5.4
install:
- pip install -q Django==$DJANGO --use-mirrors
- pip install -q django-nose --use-mirrors
@@ -14,4 +14,4 @@ install:
before_script:
- "./lint.sh"
script:
- - python runtests.py
+ - python runtests.py
2  notification/backends/__init__.py
View
@@ -32,7 +32,7 @@ def load_backends():
# import the module and get the module from sys.modules
__import__(backend_mod)
mod = sys.modules[backend_mod]
- except ImportError, e:
+ except ImportError as e:
raise exceptions.ImproperlyConfigured(
"Error importing notification backend {}: \"{}\"".format(backend_mod, e)
)
26 notification/compat.py
View
@@ -0,0 +1,26 @@
+import django
+from django.conf import settings
+from django.utils import six
+
+
+# Django 1.5 add support for custom auth user model
+if django.VERSION >= (1, 5):
+ AUTH_USER_MODEL = settings.AUTH_USER_MODEL
+else:
+ AUTH_USER_MODEL = "auth.User"
+
+try:
+ from django.contrib.auth import get_user_model
+except ImportError:
+ from django.contrib.auth.models import User
+ get_user_model = lambda: User
+
+try:
+ from urllib import quote
+except ImportError:
+ from urllib.parse import quote
+
+if six.PY3:
+ from threading import get_ident
+else:
+ from thread import get_ident
6 notification/engine.py
View
@@ -2,13 +2,13 @@
import time
import logging
import traceback
-
-import cPickle as pickle
+import base64
from django.conf import settings
from django.core.mail import mail_admins
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
+from django.utils.six.moves import cPickle as pickle # pylint: disable-msg=F
from notification.lockfile import FileLock, AlreadyLocked, LockTimeout
from notification.models import NoticeQueueBatch
@@ -44,7 +44,7 @@ def send_all(*args):
# nesting the try statement to be Python 2.4
try:
for queued_batch in NoticeQueueBatch.objects.all():
- notices = pickle.loads(str(queued_batch.pickled_data).decode("base64"))
+ notices = pickle.loads(base64.b64decode(queued_batch.pickled_data))
for user, label, extra_context, sender in notices:
try:
user = User.objects.get(pk=user)
10 notification/lockfile.py
View
@@ -12,7 +12,7 @@
... except AlreadyLocked:
... print "somefile", "is locked already."
... except LockFailed:
-... print "somefile", "can\\"t be locked."
+... print "somefile", "can\\'t be locked."
... else:
... print "got lock"
got lock
@@ -52,11 +52,11 @@
import sys
import socket
import os
-import thread
import threading
import time
import errno
-import urllib
+
+from .compat import quote, get_ident
# Work with PEP8 and non-PEP8 versions of threading module.
if not hasattr(threading, "current_thread"):
@@ -174,7 +174,7 @@ def __init__(self, path, threaded=True):
self.pid = os.getpid()
if threaded:
name = threading.current_thread().get_name()
- tname = "%s-" % urllib.quote(name, safe="")
+ tname = "%s-" % quote(name, safe="")
else:
tname = ""
dirname = os.path.dirname(self.lock_file)
@@ -306,7 +306,7 @@ def __init__(self, path, threaded=True):
"""
LockBase.__init__(self, path, threaded)
if threaded:
- tname = "%x-" % thread.get_ident()
+ tname = "%x-" % get_ident()
else:
tname = ""
# Lock file itself is a directory. Place the unique file name into
20 notification/models.py
View
@@ -1,4 +1,7 @@
-import cPickle as pickle
+from __future__ import unicode_literals
+from __future__ import print_function
+
+import base64
from django.db import models
from django.db.models.query import QuerySet
@@ -6,8 +9,10 @@
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import get_language, activate
+from django.utils.encoding import python_2_unicode_compatible
+from django.utils.six.moves import cPickle as pickle # pylint: disable-msg=F
-from django.contrib.auth.models import User
+from .compat import AUTH_USER_MODEL
from notification import backends
@@ -28,6 +33,7 @@ def create_notice_type(label, display, description, **kwargs):
NoticeType.create(label, display, description, **kwargs)
+@python_2_unicode_compatible
class NoticeType(models.Model):
label = models.CharField(_("label"), max_length=40)
@@ -37,7 +43,7 @@ class NoticeType(models.Model):
# by default only on for media with sensitivity less than or equal to this number
default = models.IntegerField(_("default"))
- def __unicode__(self):
+ def __str__(self):
return self.label
class Meta:
@@ -66,11 +72,11 @@ def create(cls, label, display, description, default=2, verbosity=1):
if updated:
notice_type.save()
if verbosity > 1:
- print "Updated %s NoticeType" % label
+ print("Updated %s NoticeType" % label)
except cls.DoesNotExist:
cls(label=label, display=display, description=description, default=default).save()
if verbosity > 1:
- print "Created %s NoticeType" % label
+ print("Created %s NoticeType" % label)
class NoticeSetting(models.Model):
@@ -79,7 +85,7 @@ class NoticeSetting(models.Model):
of a given type to a given medium.
"""
- user = models.ForeignKey(User, verbose_name=_("user"))
+ user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("user"))
notice_type = models.ForeignKey(NoticeType, verbose_name=_("notice type"))
medium = models.CharField(_("medium"), max_length=1, choices=NOTICE_MEDIA)
send = models.BooleanField(_("send"))
@@ -204,4 +210,4 @@ def queue(users, label, extra_context=None, sender=None):
notices = []
for user in users:
notices.append((user, label, extra_context, sender))
- NoticeQueueBatch(pickled_data=pickle.dumps(notices).encode("base64")).save()
+ NoticeQueueBatch(pickled_data=base64.b64encode(pickle.dumps(notices))).save()
8 notification/tests/__init__.py
View
@@ -0,0 +1,8 @@
+from ..models import NOTICE_MEDIA
+
+
+def get_backend_id(backend_name):
+ for bid, bname in NOTICE_MEDIA:
+ if bname == backend_name:
+ return bid
+ return None
7 notification/tests/models.py
View
@@ -0,0 +1,7 @@
+from django.db import models
+from ..compat import AUTH_USER_MODEL
+
+
+class Language(models.Model):
+ user = models.ForeignKey(AUTH_USER_MODEL)
+ language = models.CharField("language", max_length=10)
9 notification/tests/templates/404.html
View
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title></title>
+</head>
+<body>
+
+</body>
+</html>
9 notification/tests/templates/account/base.html
View
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title></title>
+</head>
+<body>
+
+</body>
+</html>
22 notification/tests/test_commands.py
View
@@ -0,0 +1,22 @@
+from django.test import TestCase
+from django.test.utils import override_settings
+from django.core import management, mail
+
+from ..compat import get_user_model
+from ..models import create_notice_type, queue
+
+
+class TestManagementCmd(TestCase):
+ def setUp(self):
+ self.user = get_user_model().objects.create_user("test_user", "test@user.com", "123456")
+ self.user2 = get_user_model().objects.create_user("test_user2", "test2@user.com", "123456")
+ create_notice_type("label", "display", "description")
+
+ @override_settings(SITE_ID=1)
+ def test_emit_notices(self):
+ users = [self.user, self.user2]
+ queue(users, "label")
+ management.call_command("emit_notices")
+ self.assertEqual(len(mail.outbox), 2)
+ self.assertIn(self.user.email, mail.outbox[0].to)
+ self.assertIn(self.user2.email, mail.outbox[1].to)
126 notification/tests/test_models.py
View
@@ -0,0 +1,126 @@
+import base64
+
+from django.test import TestCase
+from django.test.utils import override_settings
+from django.conf import settings
+from django.contrib.sites.models import Site
+from django.core import mail
+from django.utils.six.moves import cPickle as pickle # pylint: disable-msg=F
+
+from ..models import NoticeType, NoticeSetting, NoticeQueueBatch
+from ..models import LanguageStoreNotAvailable
+from ..models import get_notification_language, create_notice_type, send_now, send, queue
+from ..compat import get_user_model
+
+from .models import Language
+
+from . import get_backend_id
+
+
+class BaseTest(TestCase):
+ def setUp(self):
+ self.user = get_user_model().objects.create_user("test_user", "test@user.com", "123456")
+ self.user2 = get_user_model().objects.create_user("test_user2", "test2@user.com", "123456")
+ create_notice_type("label", "display", "description")
+ self.notice_type = NoticeType.objects.get(label="label")
+
+ def tearDown(self):
+ self.user.delete()
+ self.user2.delete()
+ self.notice_type.delete()
+
+
+class TestNoticeType(TestCase):
+ def test_create_notice_type(self):
+ label = "friends_invite"
+ create_notice_type(label, "Invitation Received", "you received an invitation")
+ n = NoticeType.objects.get(label=label)
+ self.assertEqual(str(n), label)
+
+ def test_create(self):
+ label = "friends_invite"
+ NoticeType.create(label, "Invitation Received", "you received an invitation", default=2,
+ verbosity=2)
+ n = NoticeType.objects.get(label=label)
+ self.assertEqual(str(n), label)
+ # update
+ NoticeType.create(label, "Invitation for you", "you got an invitation", default=1,
+ verbosity=2)
+ n = NoticeType.objects.get(pk=n.pk)
+ self.assertEqual(n.display, "Invitation for you")
+ self.assertEqual(n.description, "you got an invitation")
+ self.assertEqual(n.default, 1)
+
+
+class TestNoticeSetting(BaseTest):
+ def test_for_user(self):
+ email_id = get_backend_id("email")
+ notice_setting = NoticeSetting.objects.create(user=self.user, notice_type=self.notice_type,
+ medium=email_id, send=False)
+ self.assertEqual(NoticeSetting.for_user(self.user, self.notice_type, email_id),
+ notice_setting)
+
+ # test default fallback
+ NoticeSetting.for_user(self.user2, self.notice_type, email_id)
+ ns2 = NoticeSetting.objects.get(user=self.user2, notice_type=self.notice_type,
+ medium=email_id)
+ self.assertTrue(ns2.send)
+
+
+class TestProcedures(BaseTest):
+ def setUp(self):
+ super(TestProcedures, self).setUp()
+ self.lang = Language.objects.create(user=self.user, language="en_US")
+ mail.outbox = []
+
+ def tearDown(self):
+ super(TestProcedures, self).tearDown()
+ self.lang.delete()
+ NoticeQueueBatch.objects.all().delete()
+
+ @override_settings(NOTIFICATION_LANGUAGE_MODULE="tests.Language")
+ def test_get_notification_language(self):
+ self.assertEqual(get_notification_language(self.user), "en_US")
+ self.assertRaises(LanguageStoreNotAvailable, get_notification_language, self.user2)
+ del settings.NOTIFICATION_LANGUAGE_MODULE
+ self.assertRaises(LanguageStoreNotAvailable, get_notification_language, self.user)
+
+ @override_settings(SITE_ID=1, NOTIFICATION_LANGUAGE_MODULE="tests.Language")
+ def test_send_now(self):
+ Site.objects.create(domain="localhost", name="localhost")
+ users = [self.user, self.user2]
+ send_now(users, "label")
+ self.assertEqual(len(mail.outbox), 2)
+ self.assertIn(self.user.email, mail.outbox[0].to)
+ self.assertIn(self.user2.email, mail.outbox[1].to)
+
+ @override_settings(SITE_ID=1)
+ def test_send(self):
+ self.assertRaises(AssertionError, send, queue=True, now=True)
+
+ users = [self.user, self.user2]
+ send(users, "label", now=True)
+ self.assertEqual(len(mail.outbox), 2)
+ self.assertIn(self.user.email, mail.outbox[0].to)
+ self.assertIn(self.user2.email, mail.outbox[1].to)
+
+ send(users, "label", queue=True)
+ self.assertEqual(NoticeQueueBatch.objects.count(), 1)
+ batch = NoticeQueueBatch.objects.all()[0]
+ notices = pickle.loads(base64.b64decode(batch.pickled_data))
+ self.assertEqual(len(notices), 2)
+
+ @override_settings(SITE_ID=1)
+ def test_send_default(self):
+ # default behaviout, send_now
+ users = [self.user, self.user2]
+ send(users, "label")
+ self.assertEqual(len(mail.outbox), 2)
+ self.assertEqual(NoticeQueueBatch.objects.count(), 0)
+
+ @override_settings(SITE_ID=1)
+ def test_queue_queryset(self):
+ users = get_user_model().objects.all()
+ queue(users, "label")
+ self.assertEqual(len(mail.outbox), 0)
+ self.assertEqual(NoticeQueueBatch.objects.count(), 1)
39 notification/tests/test_views.py
View
@@ -0,0 +1,39 @@
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+
+from ..compat import get_user_model
+from ..models import create_notice_type, NoticeSetting, NoticeType
+
+from . import get_backend_id
+
+
+class TestViews(TestCase):
+ def setUp(self):
+ self.user = get_user_model().objects.create_user("test_user", "test@user.com", "123456")
+
+ def test_notice_settings_login_required(self):
+ url = reverse("notification_notice_settings")
+ response = self.client.get(url)
+ self.assertRedirects(response, "/accounts/login/?next={}".format(url),
+ target_status_code=404)
+
+ def test_notice_settings(self):
+ create_notice_type("label_1", "display", "description")
+ notice_type_1 = NoticeType.objects.get(label="label_1")
+ create_notice_type("label_2", "display", "description")
+ notice_type_2 = NoticeType.objects.get(label="label_2")
+ email_id = get_backend_id("email")
+ setting = NoticeSetting.for_user(self.user, notice_type_2, email_id)
+ setting.send = False
+ setting.save()
+ self.client.login(username="test_user", password="123456")
+ response = self.client.get(reverse("notification_notice_settings"))
+ self.assertEqual(response.status_code, 200) # pylint: disable-msg=E1103
+
+ post_data = {
+ "label_2_{}".format(email_id): "on",
+ }
+ response = self.client.post(reverse("notification_notice_settings"), data=post_data)
+ self.assertEqual(response.status_code, 302) # pylint: disable-msg=E1103
+ self.assertFalse(NoticeSetting.for_user(self.user, notice_type_1, email_id).send)
+ self.assertTrue(NoticeSetting.for_user(self.user, notice_type_2, email_id).send)
1  requirements/testing.txt
View
@@ -0,0 +1 @@
+django-nose
4 runtests.py
View
@@ -15,11 +15,13 @@
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sites",
+ 'django.contrib.sessions',
"notification",
+ "notification.tests",
],
STRIPE_PUBLIC_KEY="",
STRIPE_SECRET_KEY="",
- PAYMENTS_PLANS={}
+ PAYMENTS_PLANS={},
)
from django_nose import NoseTestSuiteRunner
7 setup.py
View
@@ -16,9 +16,14 @@
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
- "Programming Language :: Python",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3.3",
"Framework :: Django",
],
include_package_data=True,
+ test_suite='runtests',
+ install_requires=[
+ 'django>=1.4',
+ ],
zip_safe=False,
)
38 tox.ini
View
@@ -0,0 +1,38 @@
+[tox]
+envlist = py27-django14,py27-django15,py27-django16,py33-django16
+
+[testenv]
+downloadcache = {toxworkdir}/cache/
+commands={envpython} runtests.py
+deps =
+ -r{toxinidir}/requirements/testing.txt
+
+[testenv:py27-django14]
+basepython = python2.7
+deps =
+ Django==1.4.8
+ {[testenv]deps}
+
+[testenv:py27-django15]
+basepython = python2.7
+deps =
+ Django==1.5.4
+ {[testenv]deps}
+
+[testenv:py27-django16]
+basepython = python2.7
+deps =
+ https://www.djangoproject.com/download/1.6b4/tarball/
+ {[testenv]deps}
+
+[testenv:py33-django15]
+basepython = python3.3
+deps =
+ Django==1.5.4
+ {[testenv]deps}
+
+[testenv:py33-django16]
+basepython = python3.3
+deps =
+ https://www.djangoproject.com/download/1.6b4/tarball/
+ {[testenv]deps}
Please sign in to comment.
Something went wrong with that request. Please try again.