diff --git a/.gitignore b/.gitignore index 42b92c3e..4afb5629 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # Packaging files: *.eggs +*.egg-info # Installation artifacts src diff --git a/.travis.yml b/.travis.yml index b12b4772..fb93f7b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,32 +1,35 @@ language: python + python: - 2.7 +- 3.6 + env: -- TOXENV=django18 - TOXENV=django111 + matrix: include: - - python: 2.7 - env: TOXENV=quality - python: 3.6 - env: TOXENV=py36 - allow_failures: - - python: 3.6 + env: TOXENV=quality + before_install: - export DJANGO_SETTINGS_MODULE=settings + install: - make install -sudo: false + script: - make test + after_success: coveralls + deploy: provider: pypi user: edx distributions: sdist bdist_wheel on: tags: true - python: 2.7 + python: 3.6 condition: '$TOXENV = django111' password: secure: oVeS9OrvR5XvJMg86eO004xk9KFh9IbUxWqyWYYFZf1drtrzG2Bm7+F0BgF1hY2l+qyHXinPCCeBmV9CEfav80se1r8K0JFu9MwBHQkvaeBAJV+ItQ2ZumtgNNONoL5VZHIRbEYwl/lbZCWaXpmDLkJSlRVYCECP2bH+RcZkbQM= diff --git a/manage.py b/manage.py index f9726f9e..91634b45 100755 --- a/manage.py +++ b/manage.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import absolute_import, unicode_literals import os import sys diff --git a/milestones/__init__.py b/milestones/__init__.py index 771dd3cf..358974d3 100644 --- a/milestones/__init__.py +++ b/milestones/__init__.py @@ -1,4 +1,5 @@ """ Milestones app initialization module """ -__version__ = '0.1.13' +from __future__ import unicode_literals +__version__ = '0.2.0' diff --git a/milestones/admin.py b/milestones/admin.py index 8341249c..c97be9dd 100644 --- a/milestones/admin.py +++ b/milestones/admin.py @@ -1,6 +1,7 @@ """ Admin module for milestones app """ +from __future__ import absolute_import, unicode_literals from django.contrib import admin from milestones.models import ( diff --git a/milestones/api.py b/milestones/api.py index 10631d3c..c0f6e271 100644 --- a/milestones/api.py +++ b/milestones/api.py @@ -12,6 +12,7 @@ Note the terminology difference at this layer vs. Data -- add/edit/get/remove """ +from __future__ import absolute_import, unicode_literals from . import data from . import exceptions from . import validators @@ -218,7 +219,7 @@ def get_course_milestones_fulfillment_paths(course_key, user): # Build the set of fulfillment paths for the outstanding milestones fulfillment_paths = {} for milestone in required_milestones: - dict_key = '{}.{}'.format(milestone['namespace'].encode('utf-8'), milestone['name'].encode('utf-8')) + dict_key = '{}.{}'.format(milestone['namespace'], milestone['name']) fulfillment_paths[dict_key] = {} milestone_courses = data.fetch_milestone_courses( milestone, diff --git a/milestones/data.py b/milestones/data.py index c02acd53..d6bec719 100644 --- a/milestones/data.py +++ b/milestones/data.py @@ -24,11 +24,13 @@ else: import milestones.resources as remote """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals + +import six + from . import exceptions from . import models as internal from . import serializers -import six # PRIVATE/INTERNAL METHODS (public methods located further down) @@ -176,7 +178,7 @@ def fetch_milestone(milestone_id): exceptions.raise_exception("Milestone", {'id': milestone_id}, exceptions.InvalidMilestoneException) milestone = {'id': milestone_id} milestones = fetch_milestones(milestone) - if not len(milestones): + if not milestones: exceptions.raise_exception("Milestone", milestone, exceptions.InvalidMilestoneException) return milestones[0] diff --git a/milestones/exceptions.py b/milestones/exceptions.py index 8200f7e9..6bd44ea7 100644 --- a/milestones/exceptions.py +++ b/milestones/exceptions.py @@ -1,6 +1,7 @@ """ Application-specific exception classes used throughout the implementation """ +from __future__ import absolute_import, unicode_literals from django.core.exceptions import ValidationError diff --git a/milestones/management/__init__.py b/milestones/management/__init__.py index 2c19983d..5a051f90 100644 --- a/milestones/management/__init__.py +++ b/milestones/management/__init__.py @@ -1,3 +1,5 @@ """ Milestones management package initialization module """ + +from __future__ import unicode_literals diff --git a/milestones/management/commands/__init__.py b/milestones/management/commands/__init__.py index 4716c2ea..f31873f4 100644 --- a/milestones/management/commands/__init__.py +++ b/milestones/management/commands/__init__.py @@ -1,3 +1,5 @@ """ Milestones management commands package initialization module """ + +from __future__ import unicode_literals diff --git a/milestones/management/commands/tests/__init__.py b/milestones/management/commands/tests/__init__.py index 98e6196a..716ef7f8 100644 --- a/milestones/management/commands/tests/__init__.py +++ b/milestones/management/commands/tests/__init__.py @@ -1,3 +1,5 @@ """ Milestones management commands tests package initialization module """ + +from __future__ import unicode_literals diff --git a/milestones/migrations/0001_initial.py b/milestones/migrations/0001_initial.py index fa5f353f..3f449261 100644 --- a/milestones/migrations/0001_initial.py +++ b/milestones/migrations/0001_initial.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from __future__ import absolute_import from django.db import models, migrations import django.utils.timezone import model_utils.fields diff --git a/milestones/migrations/0002_data__seed_relationship_types.py b/milestones/migrations/0002_data__seed_relationship_types.py index 532fd9e1..9e74bef8 100644 --- a/milestones/migrations/0002_data__seed_relationship_types.py +++ b/milestones/migrations/0002_data__seed_relationship_types.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from __future__ import absolute_import from django.db import migrations, models from milestones.data import fetch_milestone_relationship_types diff --git a/milestones/migrations/0003_coursecontentmilestone_requirements.py b/milestones/migrations/0003_coursecontentmilestone_requirements.py index 168b289e..14e037df 100644 --- a/milestones/migrations/0003_coursecontentmilestone_requirements.py +++ b/milestones/migrations/0003_coursecontentmilestone_requirements.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from __future__ import absolute_import from django.db import migrations, models diff --git a/milestones/migrations/0004_auto_20151221_1445.py b/milestones/migrations/0004_auto_20151221_1445.py index f4dff7ab..9ce68b2c 100644 --- a/milestones/migrations/0004_auto_20151221_1445.py +++ b/milestones/migrations/0004_auto_20151221_1445.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from __future__ import absolute_import from django.db import migrations, models diff --git a/milestones/migrations/__init__.py b/milestones/migrations/__init__.py index 9f1d05d7..1fb2cf03 100644 --- a/milestones/migrations/__init__.py +++ b/milestones/migrations/__init__.py @@ -1,3 +1,5 @@ """ Milestones migrations package initialization module """ + +from __future__ import unicode_literals diff --git a/milestones/models.py b/milestones/models.py index be1066ce..41a4f563 100644 --- a/milestones/models.py +++ b/milestones/models.py @@ -8,10 +8,13 @@ which leverages Django's signal framework. """ +from __future__ import absolute_import, unicode_literals from django.db import models +from django.utils.encoding import python_2_unicode_compatible from model_utils.models import TimeStampedModel +@python_2_unicode_compatible class Milestone(TimeStampedModel): """ A Milestone is a representation of an accomplishment which can be @@ -31,10 +34,11 @@ class Meta: """ Meta class for this Django model """ unique_together = (("namespace", "name"),) - def __unicode__(self): - return unicode(self.namespace) + def __str__(self): + return self.namespace +@python_2_unicode_compatible class MilestoneRelationshipType(TimeStampedModel): """ A MilestoneRelationshipType represents a category of link available @@ -60,8 +64,8 @@ class MilestoneRelationshipType(TimeStampedModel): description = models.TextField(blank=True) active = models.BooleanField(default=True) - def __unicode__(self): - return unicode(self.name) + def __str__(self): + return self.name @classmethod # pylint: disable=invalid-name @@ -74,6 +78,7 @@ def get_supported_milestone_relationship_types(cls): return RELATIONSHIP_TYPE_CHOICES +@python_2_unicode_compatible class CourseMilestone(TimeStampedModel): """ A CourseMilestone represents the link between a Course and a @@ -93,10 +98,11 @@ class Meta: """ Meta class for this Django model """ unique_together = (("course_id", "milestone"),) - def __unicode__(self): - return unicode("%s:%s:%s" % (self.course_id, self.milestone_relationship_type, self.milestone)) + def __str__(self): + return "%s:%s:%s" % (self.course_id, self.milestone_relationship_type, self.milestone) +@python_2_unicode_compatible class CourseContentMilestone(TimeStampedModel): """ A CourseContentMilestone represents the link between a specific @@ -124,10 +130,11 @@ class Meta: """ Meta class for this Django model """ unique_together = (("course_id", "content_id", "milestone"),) - def __unicode__(self): - return unicode("%s:%s:%s" % (self.content_id, self.milestone_relationship_type, self.milestone)) + def __str__(self): + return "%s:%s:%s" % (self.content_id, self.milestone_relationship_type, self.milestone) +@python_2_unicode_compatible class UserMilestone(TimeStampedModel): """ A UserMilestone represents an stage reached or event experienced @@ -153,5 +160,5 @@ class Meta: """ Meta class for this Django model """ unique_together = ("user_id", "milestone") - def __unicode__(self): - return unicode("%s:%s" % (self.user_id, self.milestone)) + def __str__(self): + return "%s:%s" % (self.user_id, self.milestone) diff --git a/milestones/resources.py b/milestones/resources.py index 30d8433d..32d8a9a3 100644 --- a/milestones/resources.py +++ b/milestones/resources.py @@ -12,3 +12,5 @@ This module should only be called directly by data.py, in order to maintain the intended data layer abstractions/contracts. """ + +from __future__ import unicode_literals diff --git a/milestones/serializers.py b/milestones/serializers.py index 57b9d43b..a7396d3e 100644 --- a/milestones/serializers.py +++ b/milestones/serializers.py @@ -2,6 +2,7 @@ Data layer serialization operations. Converts querysets to simple python containers (mainly arrays and dicts). """ +from __future__ import absolute_import, unicode_literals import json from . import models diff --git a/milestones/services.py b/milestones/services.py index b14fb359..717ad0fd 100644 --- a/milestones/services.py +++ b/milestones/services.py @@ -2,6 +2,7 @@ A wrapper class around requested methods exposed in api.py """ +from __future__ import absolute_import, unicode_literals import types from milestones import api as milestones_api diff --git a/milestones/tests/__init__.py b/milestones/tests/__init__.py index 4ec89e7d..cf922430 100644 --- a/milestones/tests/__init__.py +++ b/milestones/tests/__init__.py @@ -1,3 +1,5 @@ """ Milestones Tests initialization module """ + +from __future__ import unicode_literals diff --git a/milestones/tests/mocks/__init__.py b/milestones/tests/mocks/__init__.py index 3bc2611a..447b1152 100644 --- a/milestones/tests/mocks/__init__.py +++ b/milestones/tests/mocks/__init__.py @@ -1 +1,3 @@ """ Milestones Tests Mocks initialization module """ + +from __future__ import unicode_literals diff --git a/milestones/tests/mocks/resources.py b/milestones/tests/mocks/resources.py index 70315c41..ca1886ea 100644 --- a/milestones/tests/mocks/resources.py +++ b/milestones/tests/mocks/resources.py @@ -1,3 +1,5 @@ """ Mock implementations of remote dependencies for test isolation """ + +from __future__ import unicode_literals diff --git a/milestones/tests/test_api.py b/milestones/tests/test_api.py index 322b6be8..83c219ab 100644 --- a/milestones/tests/test_api.py +++ b/milestones/tests/test_api.py @@ -4,7 +4,8 @@ """ Milestones API Module Test Cases """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals + from opaque_keys.edx.keys import UsageKey import milestones.api as api diff --git a/milestones/tests/test_data.py b/milestones/tests/test_data.py index 019d7d56..fd750f41 100644 --- a/milestones/tests/test_data.py +++ b/milestones/tests/test_data.py @@ -5,7 +5,7 @@ Note: 'Unit Test: ' labels are output to the console during test runs """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import milestones.api as api import milestones.data as data import milestones.exceptions as exceptions diff --git a/milestones/tests/test_services.py b/milestones/tests/test_services.py index 08dd711f..87731bc2 100644 --- a/milestones/tests/test_services.py +++ b/milestones/tests/test_services.py @@ -2,6 +2,7 @@ Test for the xBlock service """ +from __future__ import absolute_import, unicode_literals import unittest import types diff --git a/milestones/tests/utils.py b/milestones/tests/utils.py index a90ffd81..ea93f915 100644 --- a/milestones/tests/utils.py +++ b/milestones/tests/utils.py @@ -2,6 +2,7 @@ """ Utility module for Milestones test cases """ +from __future__ import absolute_import, unicode_literals from django.contrib.auth.models import User from django.test import TestCase from opaque_keys.edx.keys import CourseKey, UsageKey diff --git a/milestones/urls.py b/milestones/urls.py index feb455c9..33995eaa 100644 --- a/milestones/urls.py +++ b/milestones/urls.py @@ -1,3 +1,5 @@ """ urls.py -- useful some day when views.py comes alive """ + +from __future__ import unicode_literals diff --git a/milestones/validators.py b/milestones/validators.py index 5192ac92..7c6ce2bb 100644 --- a/milestones/validators.py +++ b/milestones/validators.py @@ -1,14 +1,15 @@ """ Validators confirm the integrity of inbound information prior to a data.py handoff """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals + import json +import six from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey, UsageKey from .data import fetch_milestone_relationship_types -import six def course_key_is_valid(course_key): @@ -62,9 +63,9 @@ def milestone_data_is_valid(milestone_data): return False if 'id' in milestone_data and not milestone_data.get('id'): return False - if 'name' in milestone_data and not len(milestone_data.get('name')): + if 'name' in milestone_data and not milestone_data.get('name'): return False - if 'namespace' in milestone_data and not len(milestone_data.get('namespace')): + if 'namespace' in milestone_data and not milestone_data.get('namespace'): return False return True diff --git a/milestones/views.py b/milestones/views.py index c6f34157..3027cd82 100644 --- a/milestones/views.py +++ b/milestones/views.py @@ -6,3 +6,5 @@ In this particular application, the views simply hand-off to the orchestration layer, which manages the application's workflows. """ + +from __future__ import unicode_literals diff --git a/openedx.yaml b/openedx.yaml index 35a7e6e5..16da1cd9 100644 --- a/openedx.yaml +++ b/openedx.yaml @@ -1,5 +1,5 @@ oeps: - oep-7: false + oep-7: true oep-18: false tags: diff --git a/settings.py b/settings.py index 64e17d21..2181accb 100644 --- a/settings.py +++ b/settings.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals DEBUG=True TEST_MODE=True TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' diff --git a/setup.py b/setup.py index bf1e5c22..7f5002c8 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +from __future__ import absolute_import, unicode_literals from setuptools import setup, find_packages from milestones import __version__ as VERSION @@ -12,22 +13,28 @@ url='https://github.com/edx/edx-milestones', license='AGPL', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Affero General Public License v3', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', 'Framework :: Django', + 'Framework :: Django :: 1.11', ], packages=find_packages(exclude=["tests"]), install_requires=[ "django>=1.8,<2.0", "django-model-utils", "edx-opaque-keys>=0.2.1,<1.0.0", + "six", ], tests_require=[ - "coverage==3.7.1", + "coverage==4.5.3", "nose==1.3.3", "httpretty==0.8.0", "pep8==1.5.7", diff --git a/test_requirements.txt b/test_requirements.txt index 2dd73329..a2dca194 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,13 +1,13 @@ -astroid==1.3.8 # Pinning to avoid backwards incompatibility issue with pylint/pylint-django coveralls -coverage==3.7.1 +astroid==1.5.3 +edx-lint +tox +coverage==4.5.3 django_nose>=1.4.1 nose==1.3.3 httpretty==0.8.0 pep8==1.5.7 -pylint==1.2.1 pep257==0.3.2 mock==1.0.1 testfixtures==4.0.0 ddt==0.8.0 -tox>=2.3.1,<3.0.0 diff --git a/tox.ini b/tox.ini index 0884ff6e..16241db5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py36-django{18,111} +envlist = py{27,36}-django{111} [testenv] setenv = @@ -7,7 +7,6 @@ setenv = PYTHONPATH = {toxinidir} deps = - django18: Django>=1.8,<1.9 django111: Django>=1.11,<2.0 -rtest_requirements.txt