diff --git a/.pep8 b/.pep8 new file mode 100644 index 0000000..f32cb5a --- /dev/null +++ b/.pep8 @@ -0,0 +1,4 @@ +[pep8] +ignore=E501 +max_line_length=119 +exclude=settings diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..fecba8c --- /dev/null +++ b/.pylintrc @@ -0,0 +1,265 @@ +[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=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" +# I0011 locally-disabled (module-level pylint overrides) +# disabling ```duplicate-code``` due to 5 lines that are the same near the top of +# both xblock files - of course these are similar because it is a core pattern to +# use - if we separated out into a separate file, we'd have the 5 lines in a separate +# file and have 2 import statements within each file to get back the stuff - pretty +# useless warning in this specific case +disable=I0011,W0232,duplicate-code + + +[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=yes + +# 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 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[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 + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[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 + + +[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_]*)|(__.*__))$ + +# 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 +function-rgx=[a-z_][a-z0-9_]{2,50}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# 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 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=i,j,k,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 functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + + +[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 + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=120 + +# 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=' ' + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,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= + + +[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-branchs=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 + + +[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 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..bbe4321 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: python + +python: + - "2.7" + +before_install: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + +install: + - pip install -r requirements.txt + +script: + - DJANGO_SETTINGS_MODULE="settings" nosetests --with-coverage --cover-package="google_drive" --with-django + - pep8 --config=.pep8 google_drive + - pylint --rcfile=.pylintrc google_drive --report=no + +after_success: coveralls diff --git a/README.md b/README.md index 62d0fac..9c44182 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Google Drive & Calendar XBlock +Google Drive & Calendar XBlock [![Build Status](https://travis-ci.org/edx-solutions/xblock-google-drive.svg?branch=master)](https://travis-ci.org/edx-solutions/xblock-google-drive) ------------------------------ This XBlock allows embedding of Google documents and calendar, @@ -61,6 +61,12 @@ If you want to run only the integration or the unit tests, append the directory $ DJANGO_SETTINGS_MODULE="settings" nosetests --with-django tests/unit ``` +To see the coverage, run the tests using the following command: + +```bash +$ DJANGO_SETTINGS_MODULE="settings" nosetests --with-coverage --cover-package="google_drive" --with-django +``` + If you have not installed the xblock-sdk in the active virtualenv, you might also have to prepend `PYTHONPATH=".:/path/to/xblock"` to the command above. (`/path/to/xblock` is the path to the xblock-sdk, where the workbench resides). @@ -108,3 +114,12 @@ License The Google Drive & Calendar XBlocks are available under the GNU Affero General 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 +``` + + diff --git a/google_drive/__init__.py b/google_drive/__init__.py index b2456b7..6fa04c4 100644 --- a/google_drive/__init__.py +++ b/google_drive/__init__.py @@ -1,2 +1,5 @@ +""" +Google drive XBlocks +""" from .google_docs import GoogleDocumentBlock -from .google_calendar import GoogleCalendarBlock \ No newline at end of file +from .google_calendar import GoogleCalendarBlock diff --git a/google_drive/google_calendar.py b/google_drive/google_calendar.py index 021426c..b58f386 100644 --- a/google_drive/google_calendar.py +++ b/google_drive/google_calendar.py @@ -1,21 +1,26 @@ +""" +Google Calendar XBlock implementation +""" # -*- coding: utf-8 -*- # # Imports ########################################################### - -import pkg_resources -import textwrap +import logging from xblock.core import XBlock from xblock.fields import Scope, String, Integer from xblock.fragment import Fragment -from .utils import loader, AttrDict from xblockutils.publish_event import PublishEventMixin +from xblockutils.resources import ResourceLoader + +LOG = logging.getLogger(__name__) +RESOURCE_LOADER = ResourceLoader(__name__) # Classes ########################################################### -class GoogleCalendarBlock(XBlock, PublishEventMixin): + +class GoogleCalendarBlock(XBlock, PublishEventMixin): # pylint: disable=too-many-ancestors """ XBlock providing a google calendar view for a specific calendar """ @@ -28,7 +33,10 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin): calendar_id = String( 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, default="edx.org_lom804qe3ttspplj1bgeu1l3ak@group.calendar.google.com" ) @@ -42,7 +50,8 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin): 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 """ @@ -51,39 +60,49 @@ def student_view(self, context): view = self.views[self.default_view][1] - iframe = ''.format(view, self.calendar_id, self.display_name) + 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, "iframe": iframe })) - fragment.add_css(loader.load_unicode('public/css/google_calendar.css')) - fragment.add_javascript(loader.load_unicode('public/js/google_calendar.js')) + fragment.add_css(RESOURCE_LOADER.load_unicode('public/css/google_calendar.css')) + fragment.add_javascript(RESOURCE_LOADER.load_unicode('public/js/google_calendar.js')) fragment.initialize_js('GoogleCalendarBlock') 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 """ 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, - 'defaultName': self.fields['display_name']._default, - 'defaultID': self.fields['calendar_id']._default + 'defaultName': self.fields['display_name']._default, # pylint: disable=protected-access + 'defaultID': self.fields['calendar_id']._default # pylint: disable=protected-access })) - fragment.add_javascript(loader.load_unicode('public/js/google_calendar_edit.js')) - fragment.add_css(loader.load_unicode('public/css/google_edit.css')) + fragment.add_javascript(RESOURCE_LOADER.load_unicode('public/js/google_calendar_edit.js')) + fragment.add_css(RESOURCE_LOADER.load_unicode('public/css/google_edit.css')) fragment.initialize_js('GoogleCalendarEditBlock') return fragment + # suffix argument is specified for xblocks, but we are not using herein @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.calendar_id = submissions['calendar_id'] self.default_view = submissions['default_view'] diff --git a/google_drive/google_docs.py b/google_drive/google_docs.py index 925e5d9..6b345d5 100644 --- a/google_drive/google_docs.py +++ b/google_drive/google_docs.py @@ -1,9 +1,11 @@ +""" +Google Document XBlock implementation +""" # -*- coding: utf-8 -*- # # Imports ########################################################### - -import pkg_resources +import logging import textwrap import requests @@ -11,12 +13,16 @@ from xblock.fields import Scope, String from xblock.fragment import Fragment -from .utils import loader, AttrDict from xblockutils.publish_event import PublishEventMixin +from xblockutils.resources import ResourceLoader + +LOG = logging.getLogger(__name__) +RESOURCE_LOADER = ResourceLoader(__name__) # Classes ########################################################### -class GoogleDocumentBlock(XBlock, PublishEventMixin): + +class GoogleDocumentBlock(XBlock, PublishEventMixin): # pylint: disable=too-many-ancestors """ XBlock providing a google document embed link """ @@ -29,7 +35,11 @@ class GoogleDocumentBlock(XBlock, PublishEventMixin): embed_code = String( 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, default=textwrap.dedent("""