Skip to content

Commit

Permalink
Merge pull request openedx-unsupported#221 from edx/rebase-ironwood
Browse files Browse the repository at this point in the history
Updating development with ironwood code base
  • Loading branch information
ihtram authored Jun 23, 2020
2 parents 85a34a9 + b03dd0c commit c639c65
Show file tree
Hide file tree
Showing 156 changed files with 2,093 additions and 819 deletions.
44 changes: 25 additions & 19 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,42 @@
language: python
python:
- "2.7"

matrix:
include:
- python: "2.7"
env: DJANGO_SETTINGS_MODULE=settings
script:
- tox -e py27
- ./node_modules/gulp/bin/gulp.js test
- bash ./run_bokchoy_tests.sh

- python: "3.5"
env: DJANGO_SETTINGS_MODULE=settings
script:
- tox -e py35
- ./node_modules/gulp/bin/gulp.js test
- bash ./run_bokchoy_tests.sh

addons:
firefox: "46.0"
firefox: "52.0.3"

env:
- DJANGO_SETTINGS_MODULE=settings
services:
- xvfb

before_install:
- wget "https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux64.tar.gz"
- tar xfz geckodriver-v0.24.0-linux64.tar.gz
- sudo mv geckodriver /usr/local/bin
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"

install:
- npm install
- "pip install -r requirements.txt"
- "pip install -r test_requirements.txt"
- "pip install -r requirements/base.txt"
- "pip install -r requirements/testing.txt"
- "pip install coveralls==1.1"

script:
- ./manage.py migrate --settings=testserver.settings
- coverage run ./manage.py test edx_notifications
- coverage report -m
- ./node_modules/gulp/bin/gulp.js test
- bash ./run_bokchoy_tests.sh
- pep8 edx_notifications
- pylint edx_notifications --report=no

after_success: coveralls

branches:
only:
- master
- cdodge/mcka-master
- cdodge/notification-digest
- development
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
include LICENSE
include README.md
include requirements/base.txt
include requirements/testing.txt
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
upgrade: export CUSTOM_COMPILE_COMMAND=make upgrade
upgrade: ## update the requirements/*.txt files with the latest packages satisfying requirements/*.in
pip install -q pip-tools
pip-compile --upgrade -o requirements/base.txt requirements/base.in
pip-compile --upgrade -o requirements/testing.txt requirements/testing.in
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ virtualenv edxnotifications_env (just do this once)
source edxnotifications_env/bin/activate
pip install -r requirements.txt
pip install -r test_requirements.txt
pip install -r requirements/base.txt
pip install -r requirements/testing.txt
./manage.py syncdb --settings=testserver.settings
./manage.py migrate --settings=testserver.settings
Expand Down
60 changes: 60 additions & 0 deletions edx_notifications/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
Configuration for edx_notifications Django app
"""
from __future__ import absolute_import

import logging

from django.apps import AppConfig
from django.conf import settings


log = logging.getLogger(__name__)


class EdxNotificationsConfig(AppConfig):
"""
Configuration class for edx_notifications Django app
"""
name = 'edx_notifications'
verbose_name = "Notification subsystem"

def ready(self):
if settings.FEATURES.get('ENABLE_NOTIFICATIONS', False):
startup_notification_subsystem()


def startup_notification_subsystem():
"""
Initialize the Notification subsystem
"""
try:
from openedx.core.djangoapps.course_groups.scope_resolver import CourseGroupScopeResolver # pylint: disable=import-error
from student.scope_resolver import CourseEnrollmentsScopeResolver, StudentEmailScopeResolver # pylint: disable=import-error
from edx_solutions_projects.scope_resolver import GroupProjectParticipantsScopeResolver # pylint: disable=import-error
from edx_notifications.scopes import register_user_scope_resolver
from edx_notifications.namespaces import register_namespace_resolver
from util.namespace_resolver import CourseNamespaceResolver # pylint: disable=import-error, no-name-in-module
from edx_notifications import startup

startup.initialize()

# register the scope resolvers that the runtime will be providing to edx-notifications.
register_user_scope_resolver('course_enrollments', CourseEnrollmentsScopeResolver())
register_user_scope_resolver('course_group', CourseGroupScopeResolver())
register_user_scope_resolver('group_project_participants', GroupProjectParticipantsScopeResolver())
register_user_scope_resolver('group_project_workgroup', GroupProjectParticipantsScopeResolver())
register_user_scope_resolver('user_email_resolver', StudentEmailScopeResolver())

# register namespace resolver
register_namespace_resolver(CourseNamespaceResolver())
except Exception as ex: # pylint: disable=broad-except
# Note this will fail when we try to run migrations as manage.py will call startup.py
# and startup.initialze() will try to manipulate some database tables.
# We need to research how to identify when we are being started up as part of
# a migration script
log.error(
'There was a problem initializing notifications subsystem. '
'This could be because the database tables have not yet been created and '
'./manage.py lms syncdb needs to run setup.py. Error was "%s". Continuing...', str(ex)
)
3 changes: 2 additions & 1 deletion edx_notifications/background.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
All code to support background Notification triggers
"""

from edx_notifications.signals import perform_notification_scan
from __future__ import absolute_import

# import edx_notifications.timer because it will register a signal receiver
# and if that Python module is not loaded, it will not be hooked up
import edx_notifications.timer # pylint: disable=unused-import
from edx_notifications.signals import perform_notification_scan


def fire_background_notification_check():
Expand Down
41 changes: 19 additions & 22 deletions edx_notifications/base_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
Base objects that data.py uses
"""

from __future__ import absolute_import

import copy
import json
import inspect
import dateutil.parser
import copy
from datetime import datetime, timedelta

import six
import dateutil.parser
from django.utils.translation import ugettext_lazy
from freezegun.api import FakeDatetime

Expand Down Expand Up @@ -60,8 +64,6 @@ def __init__(self, *args, **kwargs): # pylint: disable=unused-argument
Initializer which takes in the type this field
should be set it is set
"""
# from django.utils.functional import __proxy__
# self._expected_types.append(__proxy__)

if not self._expected_types:
raise TypeError(
Expand Down Expand Up @@ -101,8 +103,7 @@ def __set__(self, data_object, value):

value_type = type(value)

if value and value_type not in self._expected_types:

if value and value_type not in self._expected_types: # pylint: disable=unsupported-membership-test
raise TypeError(
(
"Field expected type of '{expected}' got '{got}'"
Expand Down Expand Up @@ -130,23 +131,23 @@ class StringField(TypedField):
Specialized subclass of TypedField(unicode) as a convienence
"""

_expected_types = [unicode, str]
_expected_types = [six.text_type, str]


class LazyField(TypedField):
"""
Specialized subclass of TypedField(unicode) as a convienence for Translations support
"""

_expected_types = [unicode, str, type(ugettext_lazy())]
_expected_types = [six.text_type, str, type(ugettext_lazy())]


class IntegerField(TypedField):
"""
Specialized subclass of TypedField(int) as a convienence
"""

_expected_types = [int, long]
_expected_types = list(six.integer_types)


class BooleanField(TypedField):
Expand Down Expand Up @@ -190,18 +191,18 @@ def datetime_to_json(obj):
return json.dumps(data, default=datetime_to_json)

@classmethod
def from_json(cls, value):
def from_json(cls, _value):
"""
Deserialize from json
"""

if not value:
if not _value:
return None

_dict = json.loads(value)
_dict = json.loads(_value)

for key, value in _dict.iteritems():
if isinstance(value, basestring):
for key, value in six.iteritems(_dict):
if isinstance(value, six.string_types):
# This could be a datetime posing as a ISO8601 formatted string
# we so have to apply some heuristics here
# to see if we want to even attempt
Expand Down Expand Up @@ -269,7 +270,7 @@ class BaseDataObjectMetaClass(type):
def __new__(mcs, name, bases, attrs):
# Iterate over the TypedField attrs before they're bound to the class
# so that we don't accidentally trigger any __get__ methods
for attr_name, attr in attrs.iteritems():
for attr_name, attr in six.iteritems(attrs):
if isinstance(attr, TypedField):
attr.__name__ = attr_name

Expand All @@ -281,15 +282,11 @@ def __new__(mcs, name, bases, attrs):
return super(BaseDataObjectMetaClass, mcs).__new__(mcs, name, bases, attrs)


class BaseDataObject(object):
class BaseDataObject(six.with_metaclass(BaseDataObjectMetaClass, object)):
"""
A base class for all Notification Data Objects
"""

# assign a metaclass so that all TypedFields in a BaseDataObject derviced class get a
# __name__ attribute set which is the attribute name in the containing object
__metaclass__ = BaseDataObjectMetaClass

id = IntegerField(name='id', default=None) # pylint: disable=invalid-name

def __init__(self, **kwargs):
Expand All @@ -298,7 +295,7 @@ def __init__(self, **kwargs):
of attributes which have been explicitly declared in any subclass
"""

for key in kwargs.keys():
for key in kwargs:
value = kwargs[key]
if key in dir(self):
setattr(self, key, value)
Expand Down Expand Up @@ -361,7 +358,7 @@ def __unicode__(self):
Dump out all of our fields
"""

return unicode(self.get_fields())
return six.text_type(self.get_fields())

@classmethod
def clone(cls, src):
Expand Down
21 changes: 12 additions & 9 deletions edx_notifications/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,30 @@
Any internal Timer callbacks
"""

from __future__ import absolute_import

import abc
import logging

from edx_notifications.lib.publisher import (
publish_notification_to_user,
bulk_publish_notification_to_users,
purge_expired_notifications)
import six

from edx_notifications.scopes import resolve_user_scope
from edx_notifications.stores.store import notification_store
from edx_notifications.exceptions import ItemNotFoundError
from edx_notifications.stores.store import notification_store
from edx_notifications.lib.publisher import (
purge_expired_notifications,
publish_notification_to_user,
bulk_publish_notification_to_users
)

log = logging.getLogger(__name__)


class NotificationCallbackTimerHandler(object):
class NotificationCallbackTimerHandler(six.with_metaclass(abc.ABCMeta, object)):
"""
Interface for timer callbacks
"""

__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def notification_timer_callback(self, timer):
"""
Expand Down Expand Up @@ -132,7 +135,7 @@ def notification_timer_callback(self, timer):
channel_context=channel_context
)

except Exception, ex: # pylint: disable=broad-except
except Exception as ex: # pylint: disable=broad-except
log.exception(ex)
err_msgs.append(str(ex))

Expand Down
11 changes: 6 additions & 5 deletions edx_notifications/channels/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
Abstract base class that all NotificationChannels must implement
"""

from __future__ import absolute_import

import abc
import copy

from importlib import import_module

import six
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

Expand All @@ -26,7 +28,7 @@ def _init_channel_providers():
if not config:
raise ImproperlyConfigured("Settings not configured with NOTIFICATION_CHANNEL_PROVIDERS!")

for key, channel_config in config.iteritems():
for key, channel_config in six.iteritems(config):
if 'class' not in channel_config or 'options' not in channel_config:
msg = (
"Misconfigured NOTIFICATION_CHANNEL_PROVIDERS settings, "
Expand All @@ -40,7 +42,7 @@ def _init_channel_providers():
options = copy.deepcopy(channel_config['options'])
options['name'] = key

provider = class_(**options) # pylint: disable=star-args
provider = class_(**options)
_CHANNEL_PROVIDERS[key] = provider


Expand Down Expand Up @@ -165,7 +167,7 @@ def reset_notification_channels():
_CHANNEL_PROVIDERS_TYPE_MAPS.clear()


class BaseNotificationChannelProvider(object):
class BaseNotificationChannelProvider(six.with_metaclass(abc.ABCMeta, object)):
"""
The abstract base class that all NotificationChannelProviders
need to implement
Expand Down Expand Up @@ -205,7 +207,6 @@ def link_resolvers(self):
return self._link_resolvers

# don't allow instantiation of this class, it must be subclassed
__metaclass__ = abc.ABCMeta

def __init__(self, name=None, display_name=None, display_description=None, link_resolvers=None):
"""
Expand Down
Loading

0 comments on commit c639c65

Please sign in to comment.