From 04cebeb2257c67ba64c3a19309a848d05d0c968e Mon Sep 17 00:00:00 2001 From: jczetta Date: Mon, 29 Aug 2016 10:26:00 -0400 Subject: [PATCH 1/6] Add files for external exercise directive. --- runestone/external/__init__.py | 1 + runestone/external/external.py | 87 ++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 runestone/external/__init__.py create mode 100644 runestone/external/external.py diff --git a/runestone/external/__init__.py b/runestone/external/__init__.py new file mode 100644 index 000000000..93114cdc4 --- /dev/null +++ b/runestone/external/__init__.py @@ -0,0 +1 @@ +from .external import * \ No newline at end of file diff --git a/runestone/external/external.py b/runestone/external/external.py new file mode 100644 index 000000000..62fd180cd --- /dev/null +++ b/runestone/external/external.py @@ -0,0 +1,87 @@ +# Copyright (C) 2016 Bradley N. Miller +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +__author__ = 'jczetta' +# Most of the code from question.py and shortanswer.py, (c) Bradley N. Miller. +import os + +from docutils import nodes +from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive +from runestone.assess import Assessment +from runestone.server.componentdb import addQuestionToDB, addHTMLToDB +from runestone.common.runestonedirective import RunestoneDirective +try: + from html import escape # py3 +except ImportError: + from cgi import escape # py2 + +def setup(app): + app.add_directive('external', ExternalDirective) + app.add_node(ExternalNode, html=(visit_external_node, depart_external_node)) + +# NOTE: This external questions directive is intended for pointing students to external exercises, yet still linking the instructions to a grade. There is no autograde capability, because the activity is entiely external to the textbook. +# This directive and activecode by extension may be used as tpls during a refactor of a base class -> using inheritance in gradable directives. That's still a potential TODO. + +TEXT = """ +

%(content)s

+""" + +class ExternalNode(nodes.General, nodes.Element): + def __init__(self, options): + super(ExternalNode, self).__init__() + self.external_options = options + +def visit_external_node(self, node): + div_id = node.external_options['divid'] + components = dict(node.external_options) + components.update({'divid': div_id}) + res = TEXT % components + addHTMLToDB(div_id, components['basecourse'], res) + + self.body.append(res) + +def depart_external_node(self,node): + pass + + +class ExternalDirective(Assessment): # RunestoneDirective or Assessment inheritance? + """ +.. external:: uniqueid + + text of the question goes here + """ + required_arguments = 1 # the div id + optional_arguments = 0 + final_argument_whitespace = True + has_content = True + option_spec = RunestoneDirective.option_spec.copy() + option_spec.update({'optional': directives.flag}) + + node_class = ExternalNode + + # This run method is also meant to be the basis for a class to be inherited from. We can build up from here, and also remove all unnecessary things. + def run(self): + # Raise an error if the directive does not have contents. + self.assert_has_content() + addQuestionToDB(self) + # keeping the optional for now in case we want to use that designation for grading + self.options['optional'] = 'data-optional' if 'optional' in self.options else '' # May be useful + self.options['divid'] = self.arguments[0] + self.options['content'] = "

".join(self.content) + + external_node = ExternalNode(self.options) + + return [external_node] From 242676ea41595e1932f4c5fe158907d83ed8effa Mon Sep 17 00:00:00 2001 From: jczetta Date: Mon, 29 Aug 2016 15:50:41 -0400 Subject: [PATCH 2/6] Minimal cleanup. --- runestone/external/__init__.py | 2 +- runestone/external/external.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runestone/external/__init__.py b/runestone/external/__init__.py index 93114cdc4..ff94a4bc0 100644 --- a/runestone/external/__init__.py +++ b/runestone/external/__init__.py @@ -1 +1 @@ -from .external import * \ No newline at end of file +from .external import * diff --git a/runestone/external/external.py b/runestone/external/external.py index 62fd180cd..1f83c676d 100644 --- a/runestone/external/external.py +++ b/runestone/external/external.py @@ -57,7 +57,7 @@ def depart_external_node(self,node): pass -class ExternalDirective(Assessment): # RunestoneDirective or Assessment inheritance? +class ExternalDirective(Assessment): """ .. external:: uniqueid @@ -72,7 +72,7 @@ class ExternalDirective(Assessment): # RunestoneDirective or Assessment inherita node_class = ExternalNode - # This run method is also meant to be the basis for a class to be inherited from. We can build up from here, and also remove all unnecessary things. + # This run method is also meant to be the basis for a class to be inherited from. We can build up from here, and continue to remove all unnecessary things. def run(self): # Raise an error if the directive does not have contents. self.assert_has_content() From 951f706f05202e169b287e11544497b1a4f0bccf Mon Sep 17 00:00:00 2001 From: jczetta Date: Mon, 29 Aug 2016 15:58:34 -0400 Subject: [PATCH 3/6] This inheritance may be more sensible. --- runestone/external/external.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runestone/external/external.py b/runestone/external/external.py index 1f83c676d..3f3e7bcf6 100644 --- a/runestone/external/external.py +++ b/runestone/external/external.py @@ -57,7 +57,7 @@ def depart_external_node(self,node): pass -class ExternalDirective(Assessment): +class ExternalDirective(RunestoneDirective): """ .. external:: uniqueid From af68d9d2a8fb3abdf8ebbeba82270f4a81fa5883 Mon Sep 17 00:00:00 2001 From: jczetta Date: Tue, 30 Aug 2016 14:58:24 -0400 Subject: [PATCH 4/6] Add html to db properly. --- runestone/external/external.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/runestone/external/external.py b/runestone/external/external.py index 3f3e7bcf6..0993a36bd 100644 --- a/runestone/external/external.py +++ b/runestone/external/external.py @@ -54,7 +54,19 @@ def visit_external_node(self, node): self.body.append(res) def depart_external_node(self,node): - pass + # From activecode: + ''' This is called at the start of processing an activecode node. If activecode had recursive nodes + etc and did not want to do all of the processing in visit_ac_node any finishing touches could be + added here. + ''' + res = TEXT % node.ac_components + self.body.append(res) + + addHTMLToDB(node.ac_components['divid'], + node.ac_components['basecourse'], + "".join(self.body[self.body.index(node.delimiter) + 1:])) + + self.body.remove(node.delimiter) class ExternalDirective(RunestoneDirective): @@ -84,4 +96,5 @@ def run(self): external_node = ExternalNode(self.options) + return [external_node] From 706a94d778c5291a17845c114677eedfd2ba245f Mon Sep 17 00:00:00 2001 From: jczetta Date: Tue, 30 Aug 2016 16:51:54 -0400 Subject: [PATCH 5/6] Switch to using question.py code as template for external directive. TODO: make it easier to refactor from this basis, CSS to fix formatting to be its own and static with other 'problem set' problems. --- runestone/external/external.py | 94 +++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/runestone/external/external.py b/runestone/external/external.py index 0993a36bd..4bd8494dc 100644 --- a/runestone/external/external.py +++ b/runestone/external/external.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016 Bradley N. Miller +# Copyright (C) 2011 Bradley N. Miller # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -13,88 +13,98 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -__author__ = 'jczetta' -# Most of the code from question.py and shortanswer.py, (c) Bradley N. Miller. -import os from docutils import nodes from docutils.parsers.rst import directives +from runestone.common.runestonedirective import RunestoneDirective from docutils.parsers.rst import Directive -from runestone.assess import Assessment +from sqlalchemy import create_engine, Table, MetaData, select, delete +from runestone.server import get_dburl from runestone.server.componentdb import addQuestionToDB, addHTMLToDB from runestone.common.runestonedirective import RunestoneDirective + try: from html import escape # py3 except ImportError: from cgi import escape # py2 +__author__ = 'jczetta' +# Code template is directly from question.py at the moment, which is (c) Bradley N. Miller. +#This is intended as the basis for a potential new gradeable directive class, still potential TODO. + + def setup(app): app.add_directive('external', ExternalDirective) - app.add_node(ExternalNode, html=(visit_external_node, depart_external_node)) -# NOTE: This external questions directive is intended for pointing students to external exercises, yet still linking the instructions to a grade. There is no autograde capability, because the activity is entiely external to the textbook. -# This directive and activecode by extension may be used as tpls during a refactor of a base class -> using inheritance in gradable directives. That's still a potential TODO. + app.add_node(ExternalNode, html=(visit_external_node, depart_external_node)) -TEXT = """ -

%(content)s

-""" class ExternalNode(nodes.General, nodes.Element): - def __init__(self, options): + def __init__(self, content): super(ExternalNode, self).__init__() - self.external_options = options + self.external_options = content + def visit_external_node(self, node): - div_id = node.external_options['divid'] - components = dict(node.external_options) - components.update({'divid': div_id}) - res = TEXT % components - addHTMLToDB(div_id, components['basecourse'], res) + # Set options and format templates accordingly + # env = node.document.settings.env + res = TEMPLATE_START % node.external_options self.body.append(res) -def depart_external_node(self,node): - # From activecode: - ''' This is called at the start of processing an activecode node. If activecode had recursive nodes - etc and did not want to do all of the processing in visit_ac_node any finishing touches could be - added here. - ''' - res = TEXT % node.ac_components + addHTMLToDB(node.external_options['divid'], + node.external_options['basecourse'], + "".join("")) + + # self.body.remove(node.delimiter) + + +def depart_external_node(self, node): + # Set options and format templates accordingly + res = TEMPLATE_END % node.external_options + delimiter = "_start__{}_".format(node.external_options['divid']) + self.body.append(res) - addHTMLToDB(node.ac_components['divid'], - node.ac_components['basecourse'], - "".join(self.body[self.body.index(node.delimiter) + 1:])) - self.body.remove(node.delimiter) +# Templates to be formatted by node options +TEMPLATE_START = ''' +
+
  • + + ''' +TEMPLATE_END = ''' +
  • +
    + ''' class ExternalDirective(RunestoneDirective): """ -.. external:: uniqueid +.. external:: identifier - text of the question goes here + Content Everything here is part of the activity + Content Can include links... """ - required_arguments = 1 # the div id + required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True has_content = True option_spec = RunestoneDirective.option_spec.copy() - option_spec.update({'optional': directives.flag}) - - node_class = ExternalNode + option_spec.update({'number': directives.positive_int}) - # This run method is also meant to be the basis for a class to be inherited from. We can build up from here, and continue to remove all unnecessary things. def run(self): - # Raise an error if the directive does not have contents. - self.assert_has_content() addQuestionToDB(self) - # keeping the optional for now in case we want to use that designation for grading - self.options['optional'] = 'data-optional' if 'optional' in self.options else '' # May be useful + + self.assert_has_content() # make sure activity has something in it self.options['divid'] = self.arguments[0] - self.options['content'] = "

    ".join(self.content) - + self.options['basecourse'] = self.state.document.settings.env.config.html_context.get('basecourse', "unknown") + + self.options['name'] = self.arguments[0].strip() + external_node = ExternalNode(self.options) + self.add_name(external_node) + self.state.nested_parse(self.content, self.content_offset, external_node) return [external_node] From 62ada9ac691c904b32045a5e1c47a0080758a703 Mon Sep 17 00:00:00 2001 From: jczetta Date: Tue, 30 Aug 2016 17:00:01 -0400 Subject: [PATCH 6/6] Add some extra css. This could be cleaned up/should change as evolved. --- runestone/external/css/external.css | 109 ++++++++++++++++++++++++++++ runestone/external/external.py | 2 +- 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 runestone/external/css/external.css diff --git a/runestone/external/css/external.css b/runestone/external/css/external.css new file mode 100644 index 000000000..8768f5ec3 --- /dev/null +++ b/runestone/external/css/external.css @@ -0,0 +1,109 @@ +.modal-profile { + display:none; + min-height: 300px; + overflow: hidden; + width: 700px; + padding:25px; + border:1px solid #fff; + box-shadow: 0px 2px 7px #292929; + -moz-box-shadow: 0px 2px 7px #292929; + -webkit-box-shadow: 0px 2px 7px #292929; + border-radius:10px; + -moz-border-radius:10px; + -webkit-border-radius:10px; + background: #f2f2f2; + z-index:50; +} + +.modal-lightsout { + display:none; + position:absolute; + top:0; + left:0; + width:100%; + z-index:25; + background:#000 ; +} + +.modal-close-profile { + display:none; + position:absolute; + height: 43px; + width: 43px; + background-image: url('close.png'); + top:1px; + right:0.5px; +} + + +.ac_actions{ + text-align: center; +} + +.ac_sep { + background: #000; + display: inline-block; + margin: -15px 10px; + width: 1px; + height: 35px; + padding: 0; + border: 0; +} +.ac_section{ + position: relative; + clear:both; +} +.ac_section>* { + max-width: 500pt; + margin-left: auto; + margin-right: auto; + position:relative; +} +.ac_section .clearfix{ + position: initial; +} +.ac_output{ + display:none; + max-width: 450px; +} + +.ac_caption { + text-align: center; + font-weight: bold; +} + +.ac_caption_text { + font-weight: normal; +} +.ac_caption:before { + content: "ActiveCode: " counter(activecode) " "; + counter-increment: activecode; +} + +.active_out { + background-color:#dcdcdc; + border-radius: 6px; + min-width: 20em; + max-height: 300px; + overflow: auto; +} + +.visible-ac-canvas { + border: 2px solid black; +} + +.ac_section>.col-md-12 { + max-width: 100% !important; +} + +.full_width ol { + max-width: 100% !important; +} + +.ac-disabled { + pointer-events: none; +} + +.ac-feedback { + border: 1px solid black; +} diff --git a/runestone/external/external.py b/runestone/external/external.py index 4bd8494dc..2706ded9e 100644 --- a/runestone/external/external.py +++ b/runestone/external/external.py @@ -69,7 +69,7 @@ def depart_external_node(self, node): # Templates to be formatted by node options TEMPLATE_START = ''' -

    +
  • '''