Commit ad86b847 by Martyn James

Clean up and reorganization

parent 0c5718c7
...@@ -108,3 +108,12 @@ License ...@@ -108,3 +108,12 @@ License
The Google Drive & Calendar XBlocks are available under the GNU Affero General The Google Drive & Calendar XBlocks are available under the GNU Affero General
Public License (AGPLv3). Public License (AGPLv3).
## Installation Troubleshooting
On a Mac, some people have received errors when installing lxml, trying to find a specific header file for the compiler
Try the following if you encounter a problem:
```
CPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include/libxml2 CFLAGS=-Qunused-arguments CPPFLAGS=-Qunused-arguments pip install lxml
```
"""
Google drive XBlocks
"""
from .google_docs import GoogleDocumentBlock from .google_docs import GoogleDocumentBlock
from .google_calendar import GoogleCalendarBlock from .google_calendar import GoogleCalendarBlock
\ No newline at end of file
"""
Google Calendar XBlock implementation
"""
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Imports ########################################################### # Imports ###########################################################
import logging
import pkg_resources
import textwrap
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, String, Integer from xblock.fields import Scope, String, Integer
from xblock.fragment import Fragment from xblock.fragment import Fragment
from .utils import loader, AttrDict
from xblockutils.publish_event import PublishEventMixin from xblockutils.publish_event import PublishEventMixin
from xblockutils.resources import ResourceLoader
log = logging.getLogger(__name__)
RESOURCE_LOADER = ResourceLoader(__name__)
# Classes ########################################################### # Classes ###########################################################
class GoogleCalendarBlock(XBlock, PublishEventMixin): class GoogleCalendarBlock(XBlock, PublishEventMixin):
""" """
XBlock providing a google calendar view for a specific calendar XBlock providing a google calendar view for a specific calendar
...@@ -28,7 +33,10 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin): ...@@ -28,7 +33,10 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin):
calendar_id = String( calendar_id = String(
display_name="Public Calendar ID", display_name="Public Calendar ID",
help="Google provides an ID for publicly available calendars. In the Google Calendar, open Settings and copy the ID from the Calendar Address section into this field.", help=(
"Google provides an ID for publicly available calendars. In the Google Calendar, "
"open Settings and copy the ID from the Calendar Address section into this field."
),
scope=Scope.settings, scope=Scope.settings,
default="edx.org_lom804qe3ttspplj1bgeu1l3ak@group.calendar.google.com" default="edx.org_lom804qe3ttspplj1bgeu1l3ak@group.calendar.google.com"
) )
...@@ -42,7 +50,8 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin): ...@@ -42,7 +50,8 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin):
views = [(0, 'Week'), (1, 'Month'), (2, 'Agenda')] views = [(0, 'Week'), (1, 'Month'), (2, 'Agenda')]
def student_view(self, context): # Context argument is specified for xblocks, but we are not using herein
def student_view(self, context): # pylint: disable=unused-argument
""" """
Player view, displayed to the student Player view, displayed to the student
""" """
...@@ -51,39 +60,49 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin): ...@@ -51,39 +60,49 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin):
view = self.views[self.default_view][1] view = self.views[self.default_view][1]
iframe = '<iframe src="https://www.google.com/calendar/embed?mode={}&amp;src={}&amp;showCalendars=0" title="{}"></iframe>'.format(view, self.calendar_id, self.display_name) iframe = (
'<iframe src="https://www.google.com/calendar/embed'
'?mode={}&amp;src={}&amp;showCalendars=0" title="{}"></iframe>'
).format(
view, self.calendar_id, self.display_name
)
fragment.add_content(loader.render_template('/templates/html/google_calendar.html', { fragment.add_content(RESOURCE_LOADER.render_template('/templates/html/google_calendar.html', {
"self": self, "self": self,
"iframe": iframe "iframe": iframe
})) }))
fragment.add_css(loader.load_unicode('public/css/google_calendar.css')) fragment.add_css(RESOURCE_LOADER.load_unicode('public/css/google_calendar.css'))
fragment.add_javascript(loader.load_unicode('public/js/google_calendar.js')) fragment.add_javascript(RESOURCE_LOADER.load_unicode('public/js/google_calendar.js'))
fragment.initialize_js('GoogleCalendarBlock') fragment.initialize_js('GoogleCalendarBlock')
return fragment return fragment
def studio_view(self, context): # Context argument is specified for xblocks, but we are not using herein
def studio_view(self, context): # pylint: disable=unused-argument
""" """
Editing view in Studio Editing view in Studio
""" """
fragment = Fragment() fragment = Fragment()
fragment.add_content(loader.render_template('/templates/html/google_calendar_edit.html', { # Need to access protected members of fields to get their default value
fragment.add_content(RESOURCE_LOADER.render_template('/templates/html/google_calendar_edit.html', {
'self': self, 'self': self,
'defaultName': self.fields['display_name']._default, 'defaultName': self.fields['display_name']._default, # pylint: disable=protected-access
'defaultID': self.fields['calendar_id']._default 'defaultID': self.fields['calendar_id']._default # pylint: disable=protected-access
})) }))
fragment.add_javascript(loader.load_unicode('public/js/google_calendar_edit.js')) fragment.add_javascript(RESOURCE_LOADER.load_unicode('public/js/google_calendar_edit.js'))
fragment.add_css(loader.load_unicode('public/css/google_edit.css')) fragment.add_css(RESOURCE_LOADER.load_unicode('public/css/google_edit.css'))
fragment.initialize_js('GoogleCalendarEditBlock') fragment.initialize_js('GoogleCalendarEditBlock')
return fragment return fragment
# suffix argument is specified for xblocks, but we are not using herein
@XBlock.json_handler @XBlock.json_handler
def studio_submit(self, submissions, suffix=''): def studio_submit(self, submissions, suffix=''): # pylint: disable=unused-argument
"""
Change the settings for this XBlock given by the Studio user
"""
self.display_name = submissions['display_name'] self.display_name = submissions['display_name']
self.calendar_id = submissions['calendar_id'] self.calendar_id = submissions['calendar_id']
self.default_view = submissions['default_view'] self.default_view = submissions['default_view']
......
"""
Google Document XBlock implementation
"""
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Imports ########################################################### # Imports ###########################################################
import logging
import pkg_resources
import textwrap import textwrap
import requests import requests
...@@ -11,11 +13,15 @@ from xblock.core import XBlock ...@@ -11,11 +13,15 @@ from xblock.core import XBlock
from xblock.fields import Scope, String from xblock.fields import Scope, String
from xblock.fragment import Fragment from xblock.fragment import Fragment
from .utils import loader, AttrDict
from xblockutils.publish_event import PublishEventMixin from xblockutils.publish_event import PublishEventMixin
from xblockutils.resources import ResourceLoader
log = logging.getLogger(__name__)
RESOURCE_LOADER = ResourceLoader(__name__)
# Classes ########################################################### # Classes ###########################################################
class GoogleDocumentBlock(XBlock, PublishEventMixin): class GoogleDocumentBlock(XBlock, PublishEventMixin):
""" """
XBlock providing a google document embed link XBlock providing a google document embed link
...@@ -29,7 +35,11 @@ class GoogleDocumentBlock(XBlock, PublishEventMixin): ...@@ -29,7 +35,11 @@ class GoogleDocumentBlock(XBlock, PublishEventMixin):
embed_code = String( embed_code = String(
display_name="Embed Code", display_name="Embed Code",
help="Google provides an embed code for Drive documents. In the Google Drive document, from the File menu, select Publish to the Web. Modify settings as needed, click Publish, and copy the embed code into this field.", help=(
"Google provides an embed code for Drive documents. In the Google Drive document, "
"from the File menu, select Publish to the Web. Modify settings as needed, click "
"Publish, and copy the embed code into this field."
),
scope=Scope.settings, scope=Scope.settings,
default=textwrap.dedent(""" default=textwrap.dedent("""
<iframe <iframe
...@@ -45,45 +55,54 @@ class GoogleDocumentBlock(XBlock, PublishEventMixin): ...@@ -45,45 +55,54 @@ class GoogleDocumentBlock(XBlock, PublishEventMixin):
alt_text = String( alt_text = String(
display_name="Alternative Text", display_name="Alternative Text",
help="In situations where image is not available to the reader, the alternative text ensures that no information or functionality is lost.", help=(
"In situations where image is not available to the reader, the alternative "
"text ensures that no information or functionality is lost."
),
scope=Scope.settings, scope=Scope.settings,
default="" default=""
) )
def student_view(self, context): # Context argument is specified for xblocks, but we are not using herein
def student_view(self, context): # pylint: disable=unused-argument
""" """
Player view, displayed to the student Player view, displayed to the student
""" """
fragment = Fragment() fragment = Fragment()
fragment.add_content(loader.render_template('/templates/html/google_docs.html', {"self": self})) fragment.add_content(RESOURCE_LOADER.render_template('/templates/html/google_docs.html', {"self": self}))
fragment.add_css(loader.load_unicode('public/css/google_docs.css')) fragment.add_css(RESOURCE_LOADER.load_unicode('public/css/google_docs.css'))
fragment.add_javascript(loader.load_unicode('public/js/google_docs.js')) fragment.add_javascript(RESOURCE_LOADER.load_unicode('public/js/google_docs.js'))
fragment.initialize_js('GoogleDocumentBlock') fragment.initialize_js('GoogleDocumentBlock')
return fragment return fragment
def studio_view(self, context): # Context argument is specified for xblocks, but we are not using herein
def studio_view(self, context): # pylint: disable=unused-argument
""" """
Editing view in Studio Editing view in Studio
""" """
fragment = Fragment() fragment = Fragment()
fragment.add_content(loader.render_template('/templates/html/google_docs_edit.html', { # Need to access protected members of fields to get their default value
fragment.add_content(RESOURCE_LOADER.render_template('/templates/html/google_docs_edit.html', {
'self': self, 'self': self,
'defaultName': self.fields['display_name']._default 'defaultName': self.fields['display_name']._default # pylint: disable=protected-access
})) }))
fragment.add_javascript(loader.load_unicode('public/js/google_docs_edit.js')) fragment.add_javascript(RESOURCE_LOADER.load_unicode('public/js/google_docs_edit.js'))
fragment.add_css(loader.load_unicode('public/css/google_edit.css')) fragment.add_css(RESOURCE_LOADER.load_unicode('public/css/google_edit.css'))
fragment.initialize_js('GoogleDocumentEditBlock') fragment.initialize_js('GoogleDocumentEditBlock')
return fragment return fragment
# suffix argument is specified for xblocks, but we are not using herein
@XBlock.json_handler @XBlock.json_handler
def studio_submit(self, submissions, suffix=''): def studio_submit(self, submissions, suffix=''): # pylint: disable=unused-argument
"""
Change the settings for this XBlock given by the Studio user
"""
self.display_name = submissions['display_name'] self.display_name = submissions['display_name']
self.embed_code = submissions['embed_code'] self.embed_code = submissions['embed_code']
self.alt_text = submissions['alt_text'] self.alt_text = submissions['alt_text']
...@@ -92,18 +111,24 @@ class GoogleDocumentBlock(XBlock, PublishEventMixin): ...@@ -92,18 +111,24 @@ class GoogleDocumentBlock(XBlock, PublishEventMixin):
'result': 'success', 'result': 'success',
} }
# suffix argument is specified for xblocks, but we are not using herein
@XBlock.json_handler @XBlock.json_handler
def check_url(self, data, suffix=''): def check_url(self, data, suffix=''): # pylint: disable=unused-argument
"""
Checks that the given document url is accessible, and therfore assumed to be valid
"""
test_url = data['url']
try: try:
r = requests.head(data['url']) url_response = requests.head(test_url)
except: # Catch wide range of errors
except requests.exceptions.RequestException as ex:
log.debug("Unable to connect to %s - %s", test_url, unicode(ex))
return { return {
'status_code': 400, 'status_code': 400,
} }
return { return {
'status_code': r.status_code, 'status_code': url_response.status_code,
} }
@staticmethod @staticmethod
......
""" Put tests here """
""" Unit tests for google drive components """
""" Google Calendar integration tests """
from xblockutils.base_test import SeleniumBaseTest from xblockutils.base_test import SeleniumBaseTest
class GoogleCalendarBaseTest(SeleniumBaseTest): class GoogleCalendarBaseTest(SeleniumBaseTest):
""" Test class for google calendar """
module_name = __name__ module_name = __name__
default_css_selector = 'div.google-calendar-xblock-wrapper' default_css_selector = 'div.google-calendar-xblock-wrapper'
def test_calendar_publish_event(self): def test_calendar_publish_event(self):
calendar = self.go_to_page('Calendar') calendar = self.go_to_page('Calendar')
load_event_complete = calendar.find_element_by_css_selector('.load_event_complete') load_event_complete = calendar.find_element_by_css_selector('.load_event_complete')
self.assertEqual(load_event_complete.get_attribute('value'), "I've published the event that indicates that the load has completed") self.assertEqual(
load_event_complete.get_attribute('value'),
"I've published the event that indicates that the load has completed"
)
""" Google Document integration tests """
from xblockutils.base_test import SeleniumBaseTest from xblockutils.base_test import SeleniumBaseTest
class GoogleDocumentBaseTest(SeleniumBaseTest): class GoogleDocumentBaseTest(SeleniumBaseTest):
""" Test class for google document """
module_name = __name__ module_name = __name__
default_css_selector = 'div.google-docs-xblock-wrapper' default_css_selector = 'div.google-docs-xblock-wrapper'
def test_document_publish_event(self): def test_document_publish_event(self):
document = self.go_to_page('Document') document = self.go_to_page('Document')
load_event_complete = document.find_element_by_css_selector('.load_event_complete') load_event_complete = document.find_element_by_css_selector('.load_event_complete')
self.assertEqual(load_event_complete.get_attribute('value'), "I've published the event that indicates that the load has completed") self.assertEqual(
load_event_complete.get_attribute('value'),
"I've published the event that indicates that the load has completed"
)
def test_image_publish_event(self): def test_image_publish_event(self):
image = self.go_to_page('Image') image = self.go_to_page('Image')
load_event_complete = image.find_element_by_css_selector('.load_event_complete') load_event_complete = image.find_element_by_css_selector('.load_event_complete')
self.assertEqual(load_event_complete.get_attribute('value'), "I've published the event that indicates that the load has completed") self.assertEqual(
load_event_complete.get_attribute('value'),
"I've published the event that indicates that the load has completed"
)
""" Unit tests for google drive components """
""" Tests for google drive components """
import json import json
from webob import Request from webob import Request
...@@ -8,57 +9,82 @@ from xblock.runtime import KvsFieldData, DictKeyValueStore ...@@ -8,57 +9,82 @@ from xblock.runtime import KvsFieldData, DictKeyValueStore
from google_drive import GoogleDocumentBlock, GoogleCalendarBlock from google_drive import GoogleDocumentBlock, GoogleCalendarBlock
from nose.tools import ( from nose.tools import assert_equals, assert_in
assert_equals, assert_in
)
def make_request(body, method='POST'): def make_request(body, method='POST'):
""" helper to make a request """
request = Request.blank('/') request = Request.blank('/')
request.method = 'POST' request.method = 'POST'
request.body = body.encode('utf-8') request.body = body.encode('utf-8')
request.method = method request.method = method
return request return request
def make_document_block(): def make_document_block():
""" helper to construct a GoogleDocumentBlock """
runtime = WorkbenchRuntime() runtime = WorkbenchRuntime()
key_store = DictKeyValueStore() key_store = DictKeyValueStore()
db_model = KvsFieldData(key_store) db_model = KvsFieldData(key_store)
return GoogleDocumentBlock(runtime, db_model, Mock()) return GoogleDocumentBlock(runtime, db_model, Mock())
def make_calendar_block(): def make_calendar_block():
""" helper to construct a GoogleCalendarBlock """
runtime = WorkbenchRuntime() runtime = WorkbenchRuntime()
key_store = DictKeyValueStore() key_store = DictKeyValueStore()
db_model = KvsFieldData(key_store) db_model = KvsFieldData(key_store)
return GoogleCalendarBlock(runtime, db_model, Mock()) return GoogleCalendarBlock(runtime, db_model, Mock())
def test_document_templates_contents(): def test_document_templates_contents():
block = make_document_block() block = make_document_block()
student_fragment = block.render('student_view', Mock()) student_fragment = block.render('student_view', Mock())
assert_in('<div class="google-docs-xblock-wrapper"', student_fragment.content) assert_in('<div class="google-docs-xblock-wrapper"', student_fragment.content)
assert_in('Google Document', student_fragment.content) assert_in('Google Document', student_fragment.content)
assert_in('https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsGAlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000"\n frameborder="0"\n width="960"\n height="569"\n allowfullscreen="true"', student_fragment.content) assert_in(
(
'https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsGAlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop'
'=true&delayms=10000"\n frameborder="0"\n width="960"\n height="569"\n allowfullscreen="true"'
),
student_fragment.content
)
studio_fragment = block.render('studio_view', Mock()) studio_fragment = block.render('studio_view', Mock())
assert_in('<div class="wrapper-comp-settings is-active editor-with-buttons google-edit-wrapper" id="settings-tab">', studio_fragment.content) assert_in(
'<div class="wrapper-comp-settings is-active editor-with-buttons google-edit-wrapper" id="settings-tab">',
studio_fragment.content
)
assert_in('<div class="user-inputs-and-validation">', studio_fragment.content) assert_in('<div class="user-inputs-and-validation">', studio_fragment.content)
assert_in('<div class="xblock-inputs editor_content_wrapper">', studio_fragment.content) assert_in('<div class="xblock-inputs editor_content_wrapper">', studio_fragment.content)
assert_in('<div class="xblock-actions">', studio_fragment.content) assert_in('<div class="xblock-actions">', studio_fragment.content)
def test_calendar_templates_contents(): def test_calendar_templates_contents():
block = make_calendar_block() block = make_calendar_block()
student_fragment = block.render('student_view', Mock()) student_fragment = block.render('student_view', Mock())
assert_in('<div class="google-calendar-xblock-wrapper">', student_fragment.content) assert_in('<div class="google-calendar-xblock-wrapper">', student_fragment.content)
assert_in('https://www.google.com/calendar/embed?mode=Month&amp;src=edx.org_lom804qe3ttspplj1bgeu1l3ak@group.calendar.google.com&amp;showCalendars=0', student_fragment.content) assert_in(
(
'https://www.google.com/calendar/embed?mode=Month&amp;src=edx.org_lom804qe3ttspplj1bgeu1l3ak'
'@group.calendar.google.com&amp;showCalendars=0'
),
student_fragment.content
)
assert_in('Google Calendar', student_fragment.content) assert_in('Google Calendar', student_fragment.content)
studio_fragment = block.render('studio_view', Mock()) studio_fragment = block.render('studio_view', Mock())
assert_in('<div class="wrapper-comp-settings is-active editor-with-buttons google-edit-wrapper" id="settings-tab">', studio_fragment.content) assert_in(
'<div class="wrapper-comp-settings is-active editor-with-buttons google-edit-wrapper" id="settings-tab">',
studio_fragment.content
)
assert_in('<div class="user-inputs-and-validation">', studio_fragment.content) assert_in('<div class="user-inputs-and-validation">', studio_fragment.content)
assert_in('<div class="xblock-inputs editor_content_wrapper">', studio_fragment.content) assert_in('<div class="xblock-inputs editor_content_wrapper">', studio_fragment.content)
assert_in('<div class="xblock-actions">', studio_fragment.content) assert_in('<div class="xblock-actions">', studio_fragment.content)
def test_studio_document_submit(): def test_studio_document_submit():
block = make_document_block() block = make_document_block()
...@@ -75,6 +101,7 @@ def test_studio_document_submit(): ...@@ -75,6 +101,7 @@ def test_studio_document_submit():
assert_equals(block.embed_code, "<iframe>") assert_equals(block.embed_code, "<iframe>")
assert_equals(block.alt_text, "This is alt text") assert_equals(block.alt_text, "This is alt text")
def test_calendar_document_submit(): def test_calendar_document_submit():
block = make_calendar_block() block = make_calendar_block()
...@@ -91,11 +118,15 @@ def test_calendar_document_submit(): ...@@ -91,11 +118,15 @@ def test_calendar_document_submit():
assert_equals(block.calendar_id, "google1234") assert_equals(block.calendar_id, "google1234")
assert_equals(block.default_view, 1) assert_equals(block.default_view, 1)
def test_check_document_url(): def test_check_document_url():
block = make_document_block() block = make_document_block()
data = json.dumps({ data = json.dumps({
'url': "https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsGAlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000" 'url': (
"https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsGAl"
"anSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000"
)
}) })
res = block.handle('check_url', make_request(data)) res = block.handle('check_url', make_request(data))
...@@ -109,17 +140,24 @@ def test_check_document_url(): ...@@ -109,17 +140,24 @@ def test_check_document_url():
assert_equals(json.loads(res.body), {'status_code': 400}) assert_equals(json.loads(res.body), {'status_code': 400})
data = json.dumps({ data = json.dumps({
'url': "https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsdsadfGAlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000" 'url': (
"https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsdsadfG"
"AlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000"
)
}) })
res = block.handle('check_url', make_request(data)) res = block.handle('check_url', make_request(data))
assert_equals(json.loads(res.body), {'status_code': 404}) assert_equals(json.loads(res.body), {'status_code': 404})
def test_document_publish_event(): def test_document_publish_event():
block = make_document_block() block = make_document_block()
body = json.dumps({ body = json.dumps({
'url': 'https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsdsadfGAlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000', 'url': (
'https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsdsadfGAlanSo7r9z55ualwQlj-ofBQ/embed'
'?start=true&loop=true&delayms=10000'
),
'displayed_in': 'iframe', 'displayed_in': 'iframe',
'event_type': 'edx.googlecomponent.document.displayed', 'event_type': 'edx.googlecomponent.document.displayed',
}) })
...@@ -137,18 +175,25 @@ def test_document_publish_event(): ...@@ -137,18 +175,25 @@ def test_document_publish_event():
assert_equals(json.loads(res.body), {'result': 'success'}) assert_equals(json.loads(res.body), {'result': 'success'})
body = json.dumps({ body = json.dumps({
'url': 'https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsdsadfGAlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000', 'url': (
'https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsdsadfGAlanSo7r9z55ualwQlj-ofBQ/embed'
'?start=true&loop=true&delayms=10000'
),
'displayed_in': 'iframe', 'displayed_in': 'iframe',
}) })
res = block.handle('publish_event', make_request(body)) res = block.handle('publish_event', make_request(body))
assert_equals(json.loads(res.body), {'result': 'error', 'message': 'Missing event_type in JSON data'}) assert_equals(json.loads(res.body), {'result': 'error', 'message': 'Missing event_type in JSON data'})
def test_calendar_publish_event(): def test_calendar_publish_event():
block = make_calendar_block() block = make_calendar_block()
body = json.dumps({ body = json.dumps({
'url': 'https://www.google.com/calendar/embed?mode=Month&src=edx.org_lom804qe3ttspplj1bgeu1l3ak@group.calendar.google.com&showCalendars=0', 'url': (
'https://www.google.com/calendar/embed?mode=Month&src=edx.org_lom804qe3ttspplj1bgeu1l3ak'
'@group.calendar.google.com&showCalendars=0'
),
'displayed_in': 'iframe', 'displayed_in': 'iframe',
'event_type': 'edx.googlecomponent.calendar.displayed' 'event_type': 'edx.googlecomponent.calendar.displayed'
}) })
...@@ -157,7 +202,10 @@ def test_calendar_publish_event(): ...@@ -157,7 +202,10 @@ def test_calendar_publish_event():
assert_equals(json.loads(res.body), {'result': 'success'}) assert_equals(json.loads(res.body), {'result': 'success'})
body = json.dumps({ body = json.dumps({
'url': 'https://www.google.com/calendar/embed?mode=Month&src=edx.org_lom804qe3ttspplj1bgeu1l3ak@group.calendar.google.com&showCalendars=0', 'url': (
'https://www.google.com/calendar/embed?mode=Month&src=edx.org_lom804qe3ttspplj1bgeu1l3ak'
'@group.calendar.google.com&showCalendars=0'
),
'displayed_in': 'iframe', 'displayed_in': 'iframe',
}) })
res = block.handle('publish_event', make_request(body)) res = block.handle('publish_event', make_request(body))
......
# -*- coding: utf-8 -*-
#
# Imports ###########################################################
import logging
import pkg_resources
from django.template import Context, Template
from xblockutils.resources import ResourceLoader
from xblockutils.publish_event import PublishEventMixin
# Globals ###########################################################
log = logging.getLogger(__name__)
# Functions #########################################################
loader = ResourceLoader(__name__)
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS, migrations
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=
locally-disabled,
too-few-public-methods,
bad-builtin,
star-args,
abstract-class-not-used,
abstract-class-little-used,
no-init,
fixme,
too-many-lines,
no-self-use,
too-many-ancestors,
too-many-instance-attributes,
too-few-public-methods,
too-many-public-methods,
too-many-return-statements,
too-many-branches,
too-many-arguments,
too-many-locals,
duplicate-code,
import-error,
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns)$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
# Normally limited to 30 chars, but test names can be as long as they want
function-rgx=([a-z_][a-z0-9_]{2,30}|test_[a-z0-9_]+)$
# Regular expression which should only match correct method names
# Normally, should be all lower, but some exceptions for unittest methods
method-rgx=([a-z_][a-z0-9_]{2,40}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*|maxDiff|test_[a-z0-9_]+)$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct attribute names in class
# bodies
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=f,i,j,k,db,ex,Run,_,__
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__|test_.+|setUp|tearDown
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=120
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=
REQUEST,
acl_users,
aq_parent,
objects,
DoesNotExist,
can_read,
can_write,
get_url,
size,
content,
status_code,
# For factory_boy factories
create,
build,
# For xblocks
fields,
# For locations
tag,
org,
course,
category,
name,
revision,
# For django models
_meta,
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=_|dummy|unused|.*_unused
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
# May need to do this on a Mac - CPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include/libxml2 CFLAGS=-Qunused-arguments CPPFLAGS=-Qunused-arguments pip install lxml
lxml
mock
selenium
-e git+https://github.com/nosedjango/nosedjango.git@ed7d7f9aa969252ff799ec159f828eaa8c1cbc5a#egg=nosedjango-dev
-e git+https://github.com/edx-solutions/xblock-utils.git#egg=xblock-utils
# Normally we'd use no specific hash here, but master appears to have a defect, a proposed solution appears in the git hash below
-e git+https://github.com/edx/xblock-sdk.git@c251b8924ef5bf89fb59a0a5df2723114cdf4154#egg=xblock-sdk
-e git+https://github.com/edx/XBlock.git#egg=XBlock
-e git+https://github.com/edx/bok-choy.git#egg=bok-choy
-e . -e .
-e git://github.com/nosedjango/nosedjango.git@ed7d7f9aa969252ff799ec159f828eaa8c1cbc5a#egg=nosedjango-dev
-e git://github.com/edx-solutions/xblock-utils.git#egg=xblock-utils
...@@ -31,7 +31,10 @@ setup( ...@@ -31,7 +31,10 @@ setup(
'XBlock', 'XBlock',
'xblock-utils', 'xblock-utils',
], ],
dependency_links = ['http://github.com/edx-solutions/xblock-utils/tarball/master#egg=xblock-utils'], dependency_links=[
'lxml',
'http://github.com/edx-solutions/xblock-utils.git@master#egg=xblock-utils'
],
entry_points={ entry_points={
'xblock.v1': [ 'xblock.v1': [
'google-document = google_drive:GoogleDocumentBlock', 'google-document = google_drive:GoogleDocumentBlock',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment