diff --git a/.circleci/config.yml b/.circleci/config.yml index 68359996..6326b6bc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,4 +1,4 @@ -version: 2.0 +version: 2.1 common: &common working_directory: ~/repo @@ -7,9 +7,10 @@ common: &common - restore_cache: keys: - v2-deps-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} + - v2-deps- - run: name: install dependencies - command: pip install --user tox + command: pip install --user tox codecov "coverage<5" - run: name: run tox command: ~/.local/bin/tox @@ -18,7 +19,6 @@ common: &common command: | if [[ "$UPLOAD_COVERAGE" != 0 ]]; then PATH=$HOME/.local/bin:$PATH - pip install --user codecov coverage xml ~/.local/bin/codecov --required -X search gcov pycov -f coverage.xml --flags $CIRCLE_JOB fi @@ -34,62 +34,55 @@ jobs: lint: <<: *common docker: - - image: circleci/python:3.6 + - image: circleci/python:3.8 environment: - TOXENV=checkqa - UPLOAD_COVERAGE=0 - py27dj111: - <<: *common - docker: - - image: circleci/python:2.7 - environment: - TOXENV=py27-dj111 - py34dj111: + py36dj22: <<: *common docker: - - image: circleci/python:3.4 + - image: circleci/python:3.6 environment: - TOXENV=py34-dj111 - py34dj20: + TOXENV=py36-dj22 + py36dj30: <<: *common docker: - - image: circleci/python:3.4 + - image: circleci/python:3.6 environment: - TOXENV=py34-dj20 - py35dj111: + TOXENV=py36-dj30 + py37dj22: <<: *common docker: - - image: circleci/python:3.5 + - image: circleci/python:3.7 environment: - TOXENV=py35-dj111 - py35dj20: + TOXENV=py37-dj22 + py37dj30: <<: *common docker: - - image: circleci/python:3.5 + - image: circleci/python:3.7 environment: - TOXENV=py35-dj20 - py36dj111: + TOXENV=py37-dj30 + py38dj22: <<: *common docker: - - image: circleci/python:3.6 + - image: circleci/python:3.8 environment: - TOXENV=py36-dj111 - py36dj20: + TOXENV=py38-dj22 + py38dj30: <<: *common docker: - - image: circleci/python:3.6 + - image: circleci/python:3.8 environment: - TOXENV=py36-dj20 + TOXENV=py38-dj30 workflows: version: 2 test: jobs: - lint - - py27dj111 - - py34dj111 - - py34dj20 - - py35dj111 - - py35dj20 - - py36dj111 - - py36dj20 + - py36dj22 + - py36dj30 + - py37dj22 + - py37dj30 + - py38dj22 + - py38dj30 diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 3c50558f..00000000 --- a/.coveragerc +++ /dev/null @@ -1,7 +0,0 @@ -[run] -source = pinax -omit = pinax/notifications/tests/*,pinax/notifications/admin.py -branch = 1 - -[report] -omit = pinax/notifications/tests/*,pinax/notifications/admin.py diff --git a/.gitignore b/.gitignore index 3ea1898a..bcdb04a9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ pip-delete-this-directory.txt htmlcov/ .tox/ .coverage +.coveragerc .cache nosetests.xml coverage.xml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 0b7d055c..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,161 +0,0 @@ -# How to Contribute - -There are many ways you can help contribute to pinax-notifications. Contributing -code, writing documentation, reporting bugs, as well as reading and providing -feedback on issues and pull requests, all are valid and necessary ways to -help. - -## Committing Code - -The great thing about using a distributed versioning control system like git -is that everyone becomes a committer. When other people write good patches -it makes it very easy to include their fixes/features and give them proper -credit for the work. - -We recommend that you do all your work in a separate branch. When you -are ready to work on a bug or a new feature create yourself a new branch. The -reason why this is important is you can commit as often you like. When you are -ready you can merge in the change. Let's take a look at a common workflow: - - git checkout -b task-566 - ... fix and git commit often ... - git push origin task-566 - -The reason we have created two new branches is to stay off of `master`. -Keeping master clean of only upstream changes makes yours and ours lives -easier. You can then send us a pull request for the fix/feature. Then we can -easily review it and merge it when ready. - - -### Writing Commit Messages - -Writing a good commit message makes it simple for us to identify what your -commit does from a high-level. There are some basic guidelines we'd like to -ask you to follow. - -A critical part is that you keep the **first** line as short and sweet -as possible. This line is important because when git shows commits and it has -limited space or a different formatting option is used the first line becomes -all someone might see. If your change isn't something non-trivial or there -reasoning behind the change is not obvious, then please write up an extended -message explaining the fix, your rationale, and anything else relevant for -someone else that might be reviewing the change. Lastly, if there is a -corresponding issue in Github issues for it, use the final line to provide -a message that will link the commit message to the issue and auto-close it -if appropriate. - - Add ability to travel back in time - - You need to be driving 88 miles per hour to generate 1.21 gigawatts of - power to properly use this feature. - - Fixes #88 - - -## Coding style - -When writing code to be included in pinax-notifications keep our style in mind: - -* Follow [PEP8](http://www.python.org/dev/peps/pep-0008/) there are some - cases where we do not follow PEP8. It is an excellent starting point. -* Follow [Django's coding style](http://docs.djangoproject.com/en/dev/internals/contributing/#coding-style) - we're pretty much in agreement on Django style outlined there. - -We would like to enforce a few more strict guides not outlined by PEP8 or -Django's coding style: - -* PEP8 tries to keep line length at 80 characters. We follow it when we can, - but not when it makes a line harder to read. It is okay to go a little bit - over 80 characters if not breaking the line improves readability. -* Use double quotes not single quotes. Single quotes are allowed in cases - where a double quote is needed in the string. This makes the code read - cleaner in those cases. -* Blank lines should contain no whitespace. -* Docstrings always use three double quotes on a line of their own, so, for - example, a single line docstring should take up three lines not one. -* Imports are grouped specifically and ordered alphabetically. This is shown - in the example below. -* Always use `reverse` and never `@models.permalink`. -* Tuples should be reserved for positional data structures and not used - where a list is more appropriate. -* URL patterns should use the `url()` function rather than a tuple. - -Here is an example of these rules applied: -``` - # first set of imports are stdlib imports - # non-from imports go first then from style import in their own group - import csv - - # second set of imports are Django imports with contrib in their own - # group. - from django.db import models - from django.utils import timezone - from django.utils.translation import ugettext_lazy as _ - - from django.contrib.auth.models import User - - # third set of imports are external apps (if applicable) - from tagging.fields import TagField - - # fourth set of imports are local apps - from .compat import reverse - from .fields import MarkupField - - - class Task(models.Model): - """ - A model for storing a task. - """ - - creator = models.ForeignKey(User) - created = models.DateTimeField(default=timezone.now) - modified = models.DateTimeField(default=timezone.now) - - objects = models.Manager() - - class Meta: - verbose_name = _("task") - verbose_name_plural = _("tasks") - - def __unicode__(self): - return self.summary - - def save(self, **kwargs): - self.modified = datetime.now() - super(Task, self).save(**kwargs) - - def get_absolute_url(self): - return reverse("task_detail", kwargs={"task_id": self.pk}) - - # custom methods - - - class TaskComment(models.Model): - # ... you get the point ... - pass -``` - -## Pull Requests - -Please keep your pull requests focused on one specific thing only. If you -have a number of contributions to make, then please send seperate pull -requests. It is much easier on maintainers to receive small, well defined, -pull requests, than it is to have a single large one that batches up a -lot of unrelated commits. - -If you ended up making multiple commits for one logical change, please -rebase into a single commit. - - git rebase -i HEAD~10 # where 10 is the number of commits back you need - -This will pop up an editor with your commits and some instructions you want -to squash commits down by replacing 'pick' with 's' to have it combined with -the commit before it. You can squash multiple ones at the same time. - -When you save and exit the text editor where you were squashing commits, git -will squash them down and then present you with another editor with commit -messages. Choose the one to apply to the squashed commit (or write a new -one entirely.) Save and exit will complete the rebase. Use a forced push to -your fork. - - git push -f diff --git a/LICENSE b/LICENSE index f4607aef..c9d23959 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2012-2019 James Tauber and contributors +Copyright (c) 2012-present James Tauber and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 5aff318c..70f09ad9 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,9 @@ all: init test init: python setup.py develop - pip install detox coverage + pip install tox "coverage<5" test: coverage erase - detox + tox --parallel--safe-build coverage html diff --git a/README.md b/README.md index bc283e6b..46ac5c69 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,10 @@ ## Table of Contents * [About Pinax](#about-pinax) +* [Important Links](#important-links) * [Overview](#overview) * [Features](#features) - * [Supported Django and Python versions](#supported-django-and-python-versions) + * [Supported Django and Python Versions](#supported-django-and-python-versions) * [Documentation](#documentation) * [Installation](#installation) * [Usage](#usage) @@ -38,8 +39,18 @@ ## About Pinax -Pinax is an open-source platform built on the Django Web Framework. It is an ecosystem of reusable -Django apps, themes, and starter project templates. This collection can be found at http://pinaxproject.com. +Pinax is an open-source platform built on the Django Web Framework. It is an ecosystem of reusable Django apps, themes, and starter project templates. This collection can be found at http://pinaxproject.com. + + +## Important Links + +Where you can find what you need: +* Releases: published to [PyPI](https://pypi.org/search/?q=pinax) or tagged in app repos in the [Pinax GitHub organization](https://github.com/pinax/) +* Global documentation: [Pinax documentation website](https://pinaxproject.com/pinax/) +* App specific documentation: app repos in the [Pinax GitHub organization](https://github.com/pinax/) +* Support information: [SUPPORT.md](https://github.com/pinax/.github/blob/master/SUPPORT.md) file in the [Pinax default community health file repo](https://github.com/pinax/.github/) +* Contributing information: [CONTRIBUTING.md](https://github.com/pinax/.github/blob/master/CONTRIBUTING.md) file in the [Pinax default community health file repo](https://github.com/pinax/.github/) +* Current and historical release docs: [Pinax Wiki](https://github.com/pinax/pinax/wiki/) ## pinax-notifications @@ -57,12 +68,12 @@ configurable options as to how those notifications are to be received. `pinax-no * Ability to supply your own backend notification channels * Ability to scope notifications at the site level -#### Supported Django and Python versions +#### Supported Django and Python Versions -Django \ Python | 2.7 | 3.4 | 3.5 | 3.6 ---------------- | --- | --- | --- | --- -1.11 | * | * | * | * -2.0 | | * | * | * +Django / Python | 3.6 | 3.7 | 3.8 +--------------- | --- | --- | --- +2.2 | * | * | * +3.0 | * | * | * ## Documentation @@ -474,6 +485,13 @@ urlpatterns = [ ## Change Log +### 6.0.0 + +* Drop Django 1.11, 2.0, and 2.1, and Python 2,7, 3.4, and 3.5 support +* Add Django 2.2 and 3.0, and Python 3.6, 3.7, and 3.8 support +* Update packaging configs +* Direct users to community resources + ### 5.0.2 * Remove unneeded compatibility @@ -585,31 +603,19 @@ across the ecosystem. ## Contribute -For an overview on how contributing to Pinax works read this [blog post](http://blog.pinaxproject.com/2016/02/26/recap-february-pinax-hangout/) -and watch the included video, or read our [How to Contribute](http://pinaxproject.com/pinax/how_to_contribute/) section. -For concrete contribution ideas, please see our -[Ways to Contribute/What We Need Help With](http://pinaxproject.com/pinax/ways_to_contribute/) section. - -In case of any questions we recommend you join our [Pinax Slack team](http://slack.pinaxproject.com) -and ping us there instead of creating an issue on GitHub. Creating issues on GitHub is of course -also valid but we are usually able to help you faster if you ping us in Slack. - -We also highly recommend reading our blog post on [Open Source and Self-Care](http://blog.pinaxproject.com/2016/01/19/open-source-and-self-care/). +[Contributing](https://github.com/pinax/.github/blob/master/CONTRIBUTING.md) information can be found in the [Pinax community health file repo](https://github.com/pinax/.github). ## Code of Conduct -In order to foster a kind, inclusive, and harassment-free community, the Pinax Project -has a [code of conduct](http://pinaxproject.com/pinax/code_of_conduct/). -We ask you to treat everyone as a smart human programmer that shares an interest in Python, Django, and Pinax with you. +In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a [Code of Conduct](https://github.com/pinax/.github/blob/master/CODE_OF_CONDUCT.md). We ask you to treat everyone as a smart human programmer that shares an interest in Python, Django, and Pinax with you. ## Connect with Pinax -For updates and news regarding the Pinax Project, please follow us on Twitter [@pinaxproject](https://twitter.com/pinaxproject) -and check out our [Pinax Project blog](http://blog.pinaxproject.com). +For updates and news regarding the Pinax Project, please follow us on Twitter [@pinaxproject](https://twitter.com/pinaxproject) and check out our [Pinax Project blog](http://blog.pinaxproject.com). ## License -Copyright (c) 2012-2019 James Tauber and contributors under the [MIT license](https://opensource.org/licenses/MIT). +Copyright (c) 2012-present James Tauber and contributors under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/pinax/__init__.py b/pinax/__init__.py index 25291961..fd1aad67 100644 --- a/pinax/__init__.py +++ b/pinax/__init__.py @@ -1,2 +1,3 @@ from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) # noqa diff --git a/pinax/notifications/__init__.py b/pinax/notifications/__init__.py index afc9e434..08b8c0fd 100644 --- a/pinax/notifications/__init__.py +++ b/pinax/notifications/__init__.py @@ -1,5 +1,4 @@ import pkg_resources - default_app_config = "pinax.notifications.apps.AppConfig" __version__ = pkg_resources.get_distribution("pinax-notifications").version diff --git a/pinax/notifications/backends/base.py b/pinax/notifications/backends/base.py index d71ad72a..0d9381bb 100644 --- a/pinax/notifications/backends/base.py +++ b/pinax/notifications/backends/base.py @@ -5,7 +5,7 @@ from ..hooks import hookset -class BaseBackend(object): +class BaseBackend: """ The base backend. """ @@ -35,15 +35,15 @@ def get_formatted_messages(self, formats, label, context): format_templates = {} for fmt in formats: format_templates[fmt] = render_to_string(( - "pinax/notifications/{0}/{1}".format(label, fmt), - "pinax/notifications/{0}".format(fmt)), context) + f"pinax/notifications/{label}/{fmt}", + f"pinax/notifications/{fmt}"), context) return format_templates def default_context(self): use_ssl = getattr(settings, "PINAX_USE_SSL", False) default_http_protocol = "https" if use_ssl else "http" current_site = Site.objects.get_current() - base_url = "{0}://{1}".format(default_http_protocol, current_site.domain) + base_url = f"{default_http_protocol}://{current_site.domain}" return { "default_http_protocol": default_http_protocol, "current_site": current_site, diff --git a/pinax/notifications/backends/email.py b/pinax/notifications/backends/email.py index fc7cd03b..c5801b73 100644 --- a/pinax/notifications/backends/email.py +++ b/pinax/notifications/backends/email.py @@ -10,7 +10,7 @@ class EmailBackend(BaseBackend): spam_sensitivity = 2 def can_send(self, user, notice_type, scoping): - can_send = super(EmailBackend, self).can_send(user, notice_type, scoping) + can_send = super().can_send(user, notice_type, scoping) if can_send and user.email: return True return False diff --git a/pinax/notifications/conf.py b/pinax/notifications/conf.py index 8b9ec90b..1c0eb550 100644 --- a/pinax/notifications/conf.py +++ b/pinax/notifications/conf.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import importlib from django.apps import apps as django_apps @@ -14,10 +12,10 @@ def load_model(path): return django_apps.get_model(path) except ValueError: raise ImproperlyConfigured( - "{0} must be of the form 'app_label.model_name'".format(path) + f"{path} must be of the form 'app_label.model_name'" ) except LookupError: - raise ImproperlyConfigured("{0} has not been installed".format(path)) + raise ImproperlyConfigured(f"{path} has not been installed") def load_path_attr(path): @@ -26,11 +24,11 @@ def load_path_attr(path): try: mod = importlib.import_module(module) except ImportError as e: - raise ImproperlyConfigured("Error importing {0}: '{1}'".format(module, e)) + raise ImproperlyConfigured(f"Error importing {module}: '{e}'") try: attr = getattr(mod, attr) except AttributeError: - raise ImproperlyConfigured("Module '{0}' does not define a '{1}'".format(module, attr)) + raise ImproperlyConfigured(f"Module '{module}' does not define a '{attr}'") return attr diff --git a/pinax/notifications/engine.py b/pinax/notifications/engine.py index 4f0bf525..e56936fb 100644 --- a/pinax/notifications/engine.py +++ b/pinax/notifications/engine.py @@ -1,5 +1,6 @@ import base64 import logging +import pickle import sys import time import traceback @@ -7,7 +8,6 @@ from django.contrib.auth import get_user_model from django.contrib.sites.models import Site from django.core.mail import mail_admins -from django.utils.six.moves import cPickle as pickle # pylint: disable-msg=F from . import models as notification from .conf import settings @@ -49,7 +49,7 @@ def send_all(*args): for user, label, extra_context, sender in notices: try: user = get_user_model().objects.get(pk=user) - logging.info("emitting notice {0} to {1}".format(label, user)) + logging.info(f"emitting notice {label} to {user}") # call this once per user to be atomic and allow for logging to # accurately show how long each takes. if notification.send_now([user], label, extra_context, sender): @@ -57,7 +57,7 @@ def send_all(*args): except get_user_model().DoesNotExist: # Ignore deleted users, just warn about them logging.warning( - "not emitting notice {0} to user {1} since it does not exist".format( + "not emitting notice {} to user {} since it does not exist".format( label, user) ) @@ -76,18 +76,18 @@ def send_all(*args): _, e, _ = sys.exc_info() # email people current_site = Site.objects.get_current() - subject = "[{0} emit_notices] {1}".format(current_site.name, e) + subject = f"[{current_site.name} emit_notices] {e}" message = "\n".join( traceback.format_exception(*sys.exc_info()) # pylint: disable-msg=W0142 ) mail_admins(subject, message, fail_silently=True) # log it as critical - logging.critical("an exception occurred: {0}".format(e)) + logging.critical(f"an exception occurred: {e}") finally: logging.debug("releasing lock...") lock.release() logging.debug("released.") logging.info("") - logging.info("{0} batches, {1} sent".format(batches, sent,)) - logging.info("done in {0:.2f} seconds".format(time.time() - start_time)) + logging.info(f"{batches} batches, {sent} sent") + logging.info("done in {:.2f} seconds".format(time.time() - start_time)) diff --git a/pinax/notifications/hooks.py b/pinax/notifications/hooks.py index 38f050e9..c6b3d6b0 100644 --- a/pinax/notifications/hooks.py +++ b/pinax/notifications/hooks.py @@ -5,7 +5,7 @@ from .utils import load_media_defaults -class DefaultHookSet(object): +class DefaultHookSet: def notice_setting_for_user(self, user, notice_type, medium, scoping=None): kwargs = { @@ -39,7 +39,7 @@ def notice_setting_for_user(self, user, notice_type, medium, scoping=None): return setting -class HookProxy(object): +class HookProxy: def __getattr__(self, attr): return getattr(settings.PINAX_NOTIFICATIONS_HOOKSET, attr) diff --git a/pinax/notifications/lockfile.py b/pinax/notifications/lockfile.py index cd882976..3316538e 100644 --- a/pinax/notifications/lockfile.py +++ b/pinax/notifications/lockfile.py @@ -47,8 +47,6 @@ NotMyLock - File was locked but not by the current thread/process """ -from __future__ import division - import errno import os import socket @@ -178,10 +176,7 @@ def __init__(self, path, threaded=True): else: tname = "" dirname = os.path.dirname(self.lock_file) - self.unique_name = os.path.join(dirname, - "%s.%s%s" % (self.hostname, - tname, - self.pid)) + self.unique_name = os.path.join(dirname, f"{self.hostname}.{tname}{self.pid}") def acquire(self, timeout=None): """ @@ -245,7 +240,7 @@ class LinkFileLock(LockBase): def acquire(self, timeout=None): try: open(self.unique_name, "wb").close() - except IOError: + except OSError: raise LockFailed("failed to create %s" % self.unique_name) end_time = time.time() @@ -288,9 +283,7 @@ def is_locked(self): return os.path.exists(self.lock_file) def i_am_locking(self): - return (self.is_locked() and - os.path.exists(self.unique_name) and - os.stat(self.unique_name).st_nlink == 2) + return (self.is_locked() and os.path.exists(self.unique_name) and os.stat(self.unique_name).st_nlink == 2) def break_lock(self): if os.path.exists(self.lock_file): @@ -313,7 +306,7 @@ def __init__(self, path, threaded=True): # it. self.unique_name = os.path.join( self.lock_file, - "{}.{}{}".format(self.hostname, tname, self.pid) + f"{self.hostname}.{tname}{self.pid}" ) def attempt_acquire(self, timeout, end_time, wait): @@ -365,8 +358,7 @@ def is_locked(self): return os.path.exists(self.lock_file) def i_am_locking(self): - return (self.is_locked() and - os.path.exists(self.unique_name)) + return (self.is_locked() and os.path.exists(self.unique_name)) def break_lock(self): if os.path.exists(self.lock_file): diff --git a/pinax/notifications/migrations/0001_initial.py b/pinax/notifications/migrations/0001_initial.py index c1bae04a..cf083838 100644 --- a/pinax/notifications/migrations/0001_initial.py +++ b/pinax/notifications/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.2 on 2016-03-19 11:36 -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models import django.db.models.deletion diff --git a/pinax/notifications/migrations/0002_auto_20171003_2006.py b/pinax/notifications/migrations/0002_auto_20171003_2006.py index 1d0db5c0..3fb99846 100644 --- a/pinax/notifications/migrations/0002_auto_20171003_2006.py +++ b/pinax/notifications/migrations/0002_auto_20171003_2006.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.5 on 2017-10-03 20:06 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/pinax/notifications/models.py b/pinax/notifications/models.py index 1ffb299f..0bed5943 100644 --- a/pinax/notifications/models.py +++ b/pinax/notifications/models.py @@ -1,16 +1,13 @@ -from __future__ import print_function, unicode_literals - import base64 +import pickle from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured from django.db import models from django.db.models.query import QuerySet -from django.utils.encoding import python_2_unicode_compatible -from django.utils.six.moves import cPickle as pickle # pylint: disable-msg=F -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import activate, get_language +from django.utils.translation import ugettext_lazy as _ from .conf import settings from .hooks import hookset @@ -23,7 +20,6 @@ class LanguageStoreNotAvailable(Exception): pass -@python_2_unicode_compatible class NoticeType(models.Model): label = models.CharField(_("label"), max_length=40) diff --git a/pinax/notifications/tests/test_models.py b/pinax/notifications/tests/test_models.py index 7b0a5556..da539e0e 100644 --- a/pinax/notifications/tests/test_models.py +++ b/pinax/notifications/tests/test_models.py @@ -1,13 +1,12 @@ import base64 +import pickle from django.contrib.auth import get_user_model from django.contrib.sites.models import Site from django.core import mail from django.test import TestCase from django.test.utils import override_settings -from django.utils.six.moves import cPickle as pickle -from . import get_backend_id from ..conf import settings from ..models import ( LanguageStoreNotAvailable, @@ -19,6 +18,7 @@ send, send_now, ) +from . import get_backend_id from .models import Language @@ -74,12 +74,12 @@ def test_for_user(self): class TestProcedures(BaseTest): def setUp(self): - super(TestProcedures, self).setUp() + super().setUp() self.lang = Language.objects.create(user=self.user, language="en_US") mail.outbox = [] def tearDown(self): - super(TestProcedures, self).tearDown() + super().tearDown() self.lang.delete() NoticeQueueBatch.objects.all().delete() diff --git a/pinax/notifications/tests/test_views.py b/pinax/notifications/tests/test_views.py index 86216ba3..4e11020a 100644 --- a/pinax/notifications/tests/test_views.py +++ b/pinax/notifications/tests/test_views.py @@ -2,9 +2,9 @@ from django.test import RequestFactory, TestCase from django.urls import reverse -from . import get_backend_id from ..models import NoticeSetting, NoticeType from ..views import NoticeSettingsView +from . import get_backend_id class TestViews(TestCase): @@ -26,7 +26,7 @@ def test_notice_settings(self): request.user = self.user response = NoticeSettingsView.as_view()(request) self.assertEqual(response.status_code, 200) # pylint: disable-msg=E1103 - label = "setting-{0}-{1}".format( + label = "setting-{}-{}".format( notice_type_2.pk, email_id ) diff --git a/pinax/notifications/views.py b/pinax/notifications/views.py index 2f3cc559..8fb398fa 100644 --- a/pinax/notifications/views.py +++ b/pinax/notifications/views.py @@ -12,7 +12,7 @@ class NoticeSettingsView(TemplateView): @method_decorator(login_required) def dispatch(self, *args, **kwargs): - return super(NoticeSettingsView, self).dispatch(*args, **kwargs) + return super().dispatch(*args, **kwargs) @property def scoping(self): @@ -27,7 +27,7 @@ def setting_for_user(self, notice_type, medium_id): ) def form_label(self, notice_type, medium_id): - return "setting-{0}-{1}".format( + return "setting-{}-{}".format( notice_type.pk, medium_id ) @@ -72,7 +72,7 @@ def get_context_data(self, **kwargs): ], "rows": self.settings_table(), } - context = super(NoticeSettingsView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context.update({ "notice_types": NoticeType.objects.all(), "notice_settings": settings diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2a9acf13..00000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/setup.py b/setup.py index ff9e158b..4abcc0ba 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -VERSION = "5.0.3" +VERSION = "6.0.0" LONG_DESCRIPTION = """ .. image:: http://pinaxproject.com/pinax-design/patches/pinax-notifications.svg :target: https://pypi.python.org/pypi/pinax-notifications/ @@ -51,18 +51,18 @@ Supported Django and Python Versions ------------------------------------ -+-----------------+-----+-----+-----+-----+ -| Django / Python | 2.7 | 3.4 | 3.5 | 3.6 | -+=================+=====+=====+=====+=====+ -| 1.11 | * | * | * | * | -+-----------------+-----+-----+-----+-----+ -| 2.0 | | * | * | * | -+-----------------+-----+-----+-----+-----+ ++-----------------+-----+-----+-----+ +| Django / Python | 3.6 | 3.7 | 3.8 | ++=================+=====+=====+=====+ +| 2.2 | * | * | * | ++-----------------+-----+-----+-----+ +| 3.0 | * | * | * | ++-----------------+-----+-----+-----+ """ setup( author="Pinax Team", - author_email="team@pinaxprojects.com", + author_email="team@pinaxproject.com", description="User notification management for the Django web framework", name="pinax-notifications", long_description=LONG_DESCRIPTION, @@ -80,22 +80,21 @@ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", - 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.0', + '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', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', "Topic :: Software Development :: Libraries :: Python Modules", ], install_requires=[ - "django>=1.11", + "django>=2.2", "django-appconf>=1.0.1", ], tests_require=[ diff --git a/tox.ini b/tox.ini index 4e6e2d12..8348d22f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [flake8] -ignore = E265,E501 +ignore = E265,E501,W504 max-line-length = 100 max-complexity = 10 exclude = **/*/migrations/* @@ -15,12 +15,12 @@ skip_glob=**/*/migrations/* [coverage:run] source = pinax -omit = **/*/conf.py,**/*/tests/*,**/*/migrations/* +omit = **/*/conf.py,**/*/tests/*,**/*/migrations/*,**/*/admin.py branch = true data_file = .coverage [coverage:report] -omit = **/*/conf.py,**/*/tests/*,**/*/migrations/* +omit = **/*/conf.py,**/*/tests/*,**/*/migrations/*,**/*/admin.py exclude_lines = coverage: omit show_missing = True @@ -28,18 +28,15 @@ show_missing = True [tox] envlist = checkqa, - py27-dj{111} - py34-dj{111,20} - py35-dj{111,20} - py36-dj{111,20} - + py{36,37,38}-dj{22,30} + [testenv] passenv = CI CIRCLECI CIRCLE_* deps = - coverage + coverage<5 codecov - dj111: Django>=1.11,<1.12 - dj20: Django<2.1 + dj22: Django>=2.2,<3.0 + dj30: Django>=3.0,<3.1 master: https://github.com/django/django/tarball/master usedevelop = True @@ -52,6 +49,6 @@ commands = flake8 pinax isort --recursive --check-only --diff pinax -sp tox.ini deps = - flake8 == 3.4.1 - flake8-quotes == 0.11.0 - isort == 4.2.15 + flake8 == 3.7.9 + flake8-quotes == 2.1.1 + isort == 4.3.21