import appengine_config from common import caching from models import config from models import models from models.counters import PerfCounter import gae_mini_profiler.profiler import gae_mini_profiler.templatetags # max size for in-process jinja template cache MAX_GLOBAL_CACHE_SIZE_BYTES = 8 * 1024 * 1024 # this cache used to be memcache based; now it's in-process CAN_USE_JINJA2_TEMPLATE_CACHE = config.ConfigProperty( 'gcb_can_use_jinja2_template_cache', bool, safe_dom.Text( 'Whether jinja2 can cache bytecode of compiled templates in-process.'), default_value=True) def finalize(x): """A finalize method which will correctly handle safe_dom elements.""" if isinstance(x, safe_dom.Node) or isinstance(x, safe_dom.NodeList): return jinja2.utils.Markup(x.sanitized) return x def js_string_raw(data): """Escape a string so that it can be put in a JS quote.""" if not isinstance(data, basestring): return data
def build(cls, name, label, desc, max_size_bytes, ttl_sec, dao_class): """Build the family of classes for a process-scoped Entity cache. Args: name: Name under which cache is registered. This should be in the lower_case_and_underscores naming style label: Label for the course-level setting enabling/disabling process-level caching for this entity type. desc: Description to add to the course-level setting enabling/disabling process level caching for this entity type. max_size_bytes: Largest size the cache may take on. If adding an item to the cache would make it exceed this size, items are LRU'd out until the item fits. ttl_sec: Number of seconds after which cached entries are considered stale and a (lazy) refresh is performed. dao_class: The class of an DAO in the Entity/DTO/DAO scheme common for Course Builder data access. Used for itself and also for its references to its matching DTO, Entity classes. Returns: A ResourceCacheFactory entry containing the constellation of objects that interoperate to form a cche. """ if name in cls._CACHES: return cls._CACHES[name] config_property = config.ConfigProperty( 'gcb_can_use_%s_in_process_cache' % name, bool, desc, label=label, default_value=True) class EntityCache(caching.ProcessScopedSingleton): """This class holds in-process global cache of objects.""" @classmethod def get_cache_len(cls): # pylint: disable=protected-access return len(cls.instance()._cache.items.keys()) @classmethod def get_cache_size(cls): # pylint: disable=protected-access return cls.instance()._cache.total_size def __init__(self): self._cache = caching.LRUCache(max_size_bytes=max_size_bytes) self._cache.get_entry_size = self._get_entry_size def _get_entry_size(self, key, value): if not value: return 0 return sys.getsizeof(key) + sys.getsizeof(value) @property def cache(self): return self._cache class CacheEntry(caching.AbstractCacheEntry): """Cache entry containing an entity.""" def __init__(self, entity): self.entity = entity self.created_on = datetime.datetime.utcnow() def getsizeof(self): return (dao_class.ENTITY.getsizeof(self.entity) + sys.getsizeof(self.created_on)) def has_expired(self): age = (datetime.datetime.utcnow() - self.created_on).total_seconds() return age > ttl_sec def is_up_to_date(self, key, update): if update and self.entity: return update.updated_on == self.entity.updated_on return not update and not self.entity def updated_on(self): if self.entity: return self.entity.updated_on return None @classmethod def externalize(cls, key, entry): entity = entry.entity if not entity: return None return dao_class.DTO(entity.key().id_or_name(), transforms.loads(entity.data)) @classmethod def internalize(cls, key, entity): return cls(entity) class CacheConnection(caching.AbstractCacheConnection): PERSISTENT_ENTITY = dao_class.ENTITY CACHE_ENTRY = CacheEntry @classmethod def init_counters(cls): caching.AbstractCacheConnection.init_counters() @classmethod def is_enabled(cls): return config_property.value def __init__(self, namespace): caching.AbstractCacheConnection.__init__(self, namespace) self.cache = EntityCache.instance().cache def get_updates_when_empty(self): """Load in all ResourceBundles when cache is empty.""" q = self.PERSISTENT_ENTITY.all() for entity in caching.iter_all(q): self.put(entity.key().name(), entity) self.CACHE_UPDATE_COUNT.inc() # we don't have any updates to apply; all items are new return {} class ConnectionManager(caching.RequestScopedSingleton): """Class that provides access to in-process Entity cache. This class only supports get() and does not intercept put() or delete() and is unaware of changes to Entities made in this very process. When entites change, the changes will be picked up when new instance of this class is created. If you are watching perfomance counters, you will see EVICT and EXPIRE being incremented, but not DELETE or PUT. """ def __init__(self): # Keep a separate CacheConnection for each namespace that # makes a get() request. self._conns = {} def _conn(self, ns): connected = self._conns.get(ns) if not connected: logging.debug( 'CONNECTING a CacheConnection for namespace "%s",', ns) connected = CacheConnection.new_connection(ns) self._conns[ns] = connected return connected @classmethod def _ns(cls, app_context): if app_context: return app_context.get_namespace_name() return namespace_manager.get_namespace() def _get(self, key, namespace): found, stream = self._conn(namespace).get(key) if found and stream: return stream with utils.Namespace(namespace): entity = dao_class.ENTITY_KEY_TYPE.get_entity_by_key( dao_class.ENTITY, str(key)) if entity: self._conn(namespace).put(key, entity) return dao_class.DTO(entity.key().id_or_name(), transforms.loads(entity.data)) self._conn(namespace).CACHE_NOT_FOUND.inc() self._conn(namespace).put(key, None) return None def _get_multi(self, keys, namespace): return [self._get(key, namespace) for key in keys] @classmethod def get(cls, key, app_context=None): # pylint: disable=protected-access return cls.instance()._get(key, cls._ns(app_context)) @classmethod def get_multi(cls, keys, app_context=None): # pylint: disable=protected-access return cls.instance()._get_multi(keys, cls._ns(app_context)) cache_len = counters.PerfCounter( 'gcb-models-%sCacheConnection-cache-len' % dao_class.ENTITY.__name__, 'Total number of items in the cache') cache_len.poll_value = EntityCache.get_cache_len cache_size = counters.PerfCounter( 'gcb-models-%sCacheConnection-cache-bytes' % dao_class.ENTITY.__name__, 'Total number of bytes in the cache.') cache_size.poll_value = EntityCache.get_cache_size CacheConnection.init_counters() entry = CacheFactoryEntry(EntityCache, CacheEntry, CacheConnection, ConnectionManager, config_property, cache_len, cache_size) cls._CACHES[name] = entry return entry
from models import counters from models import courses from models import custom_modules from models import jobs from models import transforms from google.appengine.api import namespace_manager from google.appengine.api import search from google.appengine.ext import db MODULE_NAME = 'Full Text Search' CAN_INDEX_ALL_COURSES_IN_CRON = config.ConfigProperty( 'gcb_can_index_automatically', bool, safe_dom.Text( 'Whether the search module can automatically index the course daily ' 'using a cron job. If enabled, this job would index the course ' 'incrementally so that only new items or items which have not been ' 'recently indexed are indexed.'), default_value=False) SEARCH_QUERIES_MADE = counters.PerfCounter( 'gcb-search-queries-made', 'The number of student queries made to the search module.') SEARCH_RESULTS_RETURNED = counters.PerfCounter( 'gcb-search-results-returned', 'The number of search results returned across all student queries.') SEARCH_FAILURES = counters.PerfCounter( 'gcb-search-failures', 'The number of search failure messages returned across all student ' 'queries.') INDEX_NAME = 'gcb_search_index'
import time from Crypto.Cipher import AES import appengine_config from common import utils from models import config from google.appengine.api import users XSRF_SECRET_LENGTH = 20 XSRF_SECRET = config.ConfigProperty( 'gcb_xsrf_secret', str, ( 'Text used to encrypt tokens, which help prevent Cross-site request ' 'forgery (CSRF, XSRF). You can set the value to any alphanumeric text, ' 'preferably using 16-64 characters. Once you change this value, the ' 'server rejects all subsequent requests issued using an old value for ' 'this variable.'), 'course builder XSRF secret') ENCRYPTION_SECRET_LENGTH = 48 ENCRYPTION_SECRET = config.ConfigProperty( 'gcb_encryption_secret', str, ( 'Text used to encrypt messages. You can set this to any text at all, ' 'but the value must be exactly ' + str(ENCRYPTION_SECRET_LENGTH) + ' characters long. If you change this value, the server will be ' 'unable to understand items encrypted under the old key.'), 'default value of CourseBuilder encryption secret', validator=config.ValidateLength(ENCRYPTION_SECRET_LENGTH).validator)
import logging import urllib2 import json from common.schema_fields import SchemaField from models import config from models import courses from models import custom_modules from modules.programming_assignments import base from modules.programming_assignments import evaluator from modules.programming_assignments import result from modules.programming_assignments import settings from modules.mooshak import mooshak MOOSHAK_STATELESS_TARGET = config.ConfigProperty( 'mooshak_stateless_target', str, 'The Mooshak stateless target to send code to evaluate. ex: mooshak.example.com or' ' 127.0.0.0', '') MOOSHAK_STATELESS_SERVER = 'mooshak_stateless_server' class MooshakStatelessEvaluator(mooshak.MooshakEvaluator): @classmethod def send_request(cls, mooshak_ip, code, program_name, pa_id, filename, tests, lang,
import os import re from xml.etree import cElementTree import html5lib import safe_dom import webapp2 import appengine_config from common import schema_fields from models import config CAN_USE_DYNAMIC_TAGS = config.ConfigProperty( 'gcb_can_use_dynamic_tags', bool, safe_dom.Text( 'Whether lesson content can make use of custom HTML tags such as ' '<gcb-youtube videoid="...">. If this is enabled some legacy content ' 'may be rendered differently. '), default_value=True) DUPLICATE_INSTANCE_ID_MESSAGE = ( 'Error processing custom HTML tag: duplicate tag id') INVALID_HTML_TAG_MESSAGE = 'Invalid HTML tag' class BaseTag(object): """Base class for the custom HTML tags.""" @classmethod def name(cls): return cls.__name__
_REST_URL_BASE = '/rest/balancer/v1' _REST_URL_PROJECT = _REST_URL_BASE + '/project' _REST_URL_TASK = _REST_URL_BASE _STATUS = 'status' _USER_ID = 'user_id' _WORKER_DEADLINE_SECONDS = 5 _WORKER_ID = 'worker_id' _WORKER_LOCKED = 'Worker locked' _WORKER_LOCKED_MAX_RETRIES = 3 _LOG = logging.getLogger('modules.balancer.balancer') logging.basicConfig() EXTERNAL_TASK_BALANCER_REST_ENABLED = config.ConfigProperty( 'gcb_external_task_balancer_rest_enabled', bool, messages.SITE_SETTINGS_TASK_BALANCER_REST, default_value=False, label='Task Balancer REST') EXTERNAL_TASK_BALANCER_WORKER_URL = config.ConfigProperty( 'gcb_external_task_balancer_worker_url', str, messages.SITE_SETTINGS_TASK_BALANCER_URL, default_value='', label='Task Balancer URL') class Error(Exception): """Base error class.""" class NotFoundError(Exception):
from common import messages from common import schema_fields from models import config _LXML_AVAILABLE = False try: import lxml.html _LXML_AVAILABLE = True except ImportError: if appengine_config.PRODUCTION_MODE: raise CAN_USE_DYNAMIC_TAGS = config.ConfigProperty( 'gcb_can_use_dynamic_tags', bool, messages.SITE_SETTINGS_DYNAMIC_TAGS, default_value=True, label='Dynamic Tags') DUPLICATE_INSTANCE_ID_MESSAGE = ( 'Error processing custom HTML tag: duplicate tag id') INVALID_HTML_TAG_MESSAGE = 'Invalid HTML tag' class BaseTag(object): """Base class for the custom HTML tags.""" @classmethod def name(cls): return cls.__name__ @classmethod
from controllers import sites from models import config from models import courses from models import roles from models import transforms from google.appengine.api import namespace_manager from google.appengine.api import taskqueue from google.appengine.api import urlfetch from google.appengine.ext import deferred _INSTALLATION_IDENTIFIER = config.ConfigProperty( 'gcb_report_usage_identifier', str, ( 'Randomized string used to identify this installation of ' 'CourseBuilder when reporting usage statistics. This value ' 'has no intrinsic meaning, and no relation to any data or ' 'course setting; it is just used to correlate the weekly ' 'reports from this installation.'), default_value='A random value will be picked when the first report is ' 'sent.', label='Usage report ID', ) # Name of the item in the course settings dictionary which contains the # randomly-generated identifier for the course. (This name needs to be # defined here to prevent circular inclusion problems with this module # versus 'config') USAGE_REPORTING_FIELD_ID = 'usage_reporting_id' # Usage reporting is turned off on dev, but this flag overrides that, to # enable testing of messaging and UI. ENABLED_IN_DEV_FOR_TESTING = False
logging.basicConfig() VERSION_1_0 = '1.0' VERSION_1_1 = '1.1' VERSION_1_2 = '1.2' VERSIONS = frozenset([ VERSION_1_0, VERSION_1_1, VERSION_1_2, ]) COURSES_CAN_ENABLE_LTI_PROVIDER = config.ConfigProperty( 'gcb_courses_can_enable_lti_provider', bool, ('Whether or not to allow courses to enable the LTI provider for their ' 'content. If True, courses can enable a setting to allow outside parties ' 'to embed their content via the LTI protocol. If False, the control on ' 'the course page to enable the LTI provider will be present, but it will ' 'have no effect.'), default_value=True) class Error(Exception): """Base error.""" class InvalidVersionError(Exception): """Raised when an invalid LTI version is specified.""" def _get_runtime(app_context):
_WIDGET_URL = '%s/widget' % _BASE_URL _CONFIG_YAML_ADMINS_NAME = 'admins' _CONFIG_YAML_ENABLED_NAME = 'enabled' _CONFIG_YAML_PATH = os.path.join(_RESOURCES_DIR, 'config.yaml') _TEMPLATES_DIR = os.path.join(_BASE_PATH, 'templates') _BRANDING_TEMPLATE_PATH = 'branding.html' _SIGN_OUT_TEMPLATE_PATH = 'signout.html' _WIDGET_TEMPLATE_PATH = 'widget.html' _LOG = logging.getLogger('modules.gitkit.gitkit') _BROWSER_API_KEY = config.ConfigProperty( 'gcb_modules_gitkit_browser_api_key', str, ('Browser API Key from the Google Developer console. This field is found ' 'under Key from browser applications > API key. See %s for instructions') % 'https://developers.google.com/identity/toolkit/web/configure-service', default_value='') _CLIENT_ID = config.ConfigProperty( 'gcb_modules_gitkit_client_id', str, ('Client ID from the Google Developer console. This field is found under ' 'Client ID for web application > Client ID. See %s for instructions') % 'https://developers.google.com/identity/toolkit/web/configure-service', default_value='') _SERVER_API_KEY = config.ConfigProperty( 'gcb_modules_gitkit_server_api_key', str, ('Server API key from the Google Developer console. This field is found ' 'under Key for server applications > API key. See %s for instructions') % 'https://developers.google.com/identity/toolkit/web/configure-service', default_value='') _SERVICE_ACCOUNT_JSON = config.ConfigProperty(
from modules.usage_reporting import constants def _on_change_report_allowed(config_property, unused_old_value): """Callback to report externally when value of REPORT_ALLOWED changes.""" messaging.Message.send_instance_message( messaging.Message.METRIC_REPORT_ALLOWED, config_property.value, source=messaging.Message.ADMIN_SOURCE) REPORT_ALLOWED = config.ConfigProperty( 'gcb_report_usage_permitted', bool, safe_dom.Template( jinja_utils.get_template('message.html', [constants.TEMPLATES_DIR], default_locale=None)), after_change=_on_change_report_allowed, default_value=False, label='Usage reporting') def set_report_allowed(value): with common_utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME): entity = config.ConfigPropertyEntity.get_by_key_name( REPORT_ALLOWED.name) if not entity: entity = config.ConfigPropertyEntity(key_name=REPORT_ALLOWED.name) entity.value = str(value) entity.is_draft = False entity.put()
m_list = list(message) m_list.reverse() return ''.join(m_list) def encrypt(self, message): return self._reverse(message) def decrypt(self, message): return self._reverse(message) XSRF_SECRET_LENGTH = 20 XSRF_SECRET = config.ConfigProperty('gcb_xsrf_secret', str, messages.SITE_SETTINGS_XSRF_SECRET, default_value='Course Builder XSRF Secret', label='XSRF secret') ENCRYPTION_SECRET_LENGTH = 48 ENCRYPTION_SECRET = config.ConfigProperty( 'gcb_encryption_secret', str, messages.SITE_SETTINGS_ENCRYPTION_SECRET, default_value='default value of CourseBuilder encryption secret', label='Encryption Secret', validator=config.ValidateLength(ENCRYPTION_SECRET_LENGTH).validator) class EncryptionManager(object):
_LOG = logging.getLogger('modules.embed.embed') _TEMPLATES_DIR_V1 = os.path.join(_BASE_DIR, 'templates', _V1) _DEMO_HTML_PATH = os.path.join(_TEMPLATES_DIR_V1, 'demo.html') _GLOBAL_ERRORS_DEMO_HTML_PATH = os.path.join( _TEMPLATES_DIR_V1, 'global_errors.html') _LOCAL_ERRORS_DEMO_HTML_PATH = os.path.join( _TEMPLATES_DIR_V1, 'local_errors.html') _TEMPLATES_ENV = jinja_utils.create_jinja_environment( jinja2.FileSystemLoader([_TEMPLATES_DIR_V1])) # TODO(johncox): remove after security audit of embed module. _MODULE_HANDLERS_ENABLED = config.ConfigProperty( 'gcb_modules_embed_handlers_enabled', bool, ('Whether or not to enable the embed module handlers. You must enable this ' 'property to use Course Builder embeds'), default_value=False, label='Enable embed module handlers') class AbstractEnrollmentPolicy(object): """Abstract parent for business logic run during resource dispatch.""" @classmethod def apply(cls, unused_handler): raise NotImplementedError( 'You must set a concrete enrollment policy on your embed') class AutomaticEnrollmentPolicy(AbstractEnrollmentPolicy): """Policy that registers the current user in a course automatically."""
def get(self): if not GQL_SERVICE_ENABLED.value: self.error(404) return query_str = self.request.get('q') expanded_gcb_tags = self.request.get('expanded_gcb_tags') response_dict = self._get_response_dict(query_str, expanded_gcb_tags) status_code = 400 if response_dict['errors'] else 200 self._send_response(status_code, response_dict) GQL_SERVICE_ENABLED = config.ConfigProperty( 'gcb_gql_service_enabled', bool, 'Enable the GraphQL REST endpoint.', default_value=True, label='GraphQL') def register_module(): global_routes = [(GraphQLRestHandler.URL, GraphQLRestHandler)] namespaced_routes = [] global custom_module # pylint: disable=global-statement custom_module = custom_modules.Module( 'GraphQL', 'Handles queries for Course Builder in GraphQL.', global_routes, namespaced_routes)
import json from google import protobuf from common.schema_fields import SchemaField from models import config from models import courses from models import custom_modules from modules.nsjail.proto import request_pb2 from modules.programming_assignments import base from modules.programming_assignments import evaluator from modules.programming_assignments import result from modules.programming_assignments import settings NSJAIL_TARGET = config.ConfigProperty( 'nsjail_target', str, 'The Nsjail target to send code to evaluate. ex: nsjail.example.com or' ' 127.0.0.0', '') NSJAIL_SERVER = 'nsjail_server' ERROR_LIST = { request_pb2.CodeReply.MEMORY_LIMIT_EXCEEDED: 'Memory Limit Exceeded', request_pb2.CodeReply.TIME_LIMIT_EXCEEDED: 'Time Limit Exceeded', request_pb2.CodeReply.RUNTIME_ERROR: 'Runtime Error', request_pb2.CodeReply.WRONG_ANSWER: 'Wrong Answer', request_pb2.CodeReply.PRESENTATION_ERROR: 'Presentation Error', request_pb2.CodeReply.COMPILATION_ERROR: 'Compilation Error' } class NsjailEvaluator(evaluator.ProgramEvaluator):
from models import jobs from models import services from models import transforms from modules.dashboard import dashboard from google.appengine.api import namespace_manager from google.appengine.api import search from google.appengine.ext import db MODULE_NAME = 'Full Text Search' DEPRECATED = config.ConfigProperty( 'gcb_can_index_automatically', bool, safe_dom.Text( 'This property has been deprecated; it is retained so that we ' 'will not generate no-such-variable error messages for existing ' 'installations that have this property set.'), default_value=False, label='Automatically index search', deprecated=True) SEARCH_QUERIES_MADE = counters.PerfCounter( 'gcb-search-queries-made', 'The number of student queries made to the search module.') SEARCH_RESULTS_RETURNED = counters.PerfCounter( 'gcb-search-results-returned', 'The number of search results returned across all student queries.') SEARCH_FAILURES = counters.PerfCounter( 'gcb-search-failures', 'The number of search failure messages returned across all student ' 'queries.')
def add_setting(deprecated): return config.ConfigProperty( 'gcb_test_property', bool, 'doc string', label='Test Property Label', deprecated=deprecated)
import appengine_config from common import caching from common import messages from models import config from models import models from models import transforms from models.counters import PerfCounter # max size for in-process jinja template cache MAX_GLOBAL_CACHE_SIZE_BYTES = 8 * 1024 * 1024 # this cache used to be memcache based; now it's in-process CAN_USE_JINJA2_TEMPLATE_CACHE = config.ConfigProperty( 'gcb_can_use_jinja2_template_cache', bool, messages.SITE_SETTINGS_CACHE_TEMPLATES, default_value=True, label='Cache Templates') def finalize(x): """A finalize method which will correctly handle safe_dom elements.""" if isinstance(x, safe_dom.SafeDom): return jinja2.utils.Markup(x.sanitized) return x def js_string_raw(data): """Escape a string so that it can be put in a JS quote.""" if not isinstance(data, basestring): return data
_REST_URL_PROJECT = _REST_URL_BASE + '/project' _REST_URL_TASK = _REST_URL_BASE _STATUS = 'status' _USER_ID = 'user_id' _WORKER_DEADLINE_SECONDS = 5 _WORKER_ID = 'worker_id' _WORKER_LOCKED = 'Worker locked' _WORKER_LOCKED_MAX_RETRIES = 3 _LOG = logging.getLogger('modules.balancer.balancer') logging.basicConfig() EXTERNAL_TASK_BALANCER_REST_ENABLED = config.ConfigProperty( 'gcb_external_task_balancer_rest_enabled', bool, ('Whether or not to enable the REST endpoints for the external task ' 'balancer module. You must also set gcb_external_task_balancer_worker_url ' 'to use this feature.'), default_value=False) EXTERNAL_TASK_BALANCER_WORKER_URL = config.ConfigProperty( 'gcb_external_task_balancer_worker_url', str, 'URL for the worker pool used by the external task balancer module.', default_value='') class Error(Exception): """Base error class.""" class NotFoundError(Exception):
def _on_change_report_allowed(config_property, unused_old_value): """Callback to report externally when value of REPORT_ALLOWED changes.""" messaging.Message.send_instance_message( messaging.Message.METRIC_REPORT_ALLOWED, config_property.value, source=messaging.Message.ADMIN_SOURCE) REPORT_ALLOWED = config.ConfigProperty( 'gcb_report_usage_permitted', bool, 'Whether anonymized per-course usage statistics should be sent to the ' 'CourseBuilder team. The report contains randomly chosen identifiers ' 'for the installation and course (to correlate reports) ' 'hour-by-hour data on enrollments/unenrollments, and current number of ' 'students. This report is sent once a week.', after_change=_on_change_report_allowed, default_value=False, label='Usage reporting') def set_report_allowed(value): with common_utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME): entity = config.ConfigPropertyEntity.get_by_key_name( REPORT_ALLOWED.name) if not entity: entity = config.ConfigPropertyEntity(key_name=REPORT_ALLOWED.name) entity.value = str(value) entity.is_draft = False
import urllib2 from common.schema_fields import SchemaField from models import config from models import courses from models import custom_modules from modules.programming_assignments import base from modules.programming_assignments import evaluator from modules.programming_assignments import result from modules.programming_assignments import settings from xml.dom import minidom from xml.parsers.expat import ExpatError MOOSHAK_TARGET = config.ConfigProperty( 'mooshak_target', str, 'The Mooshak target to send code to evaluate. ex: mooshak.example.com or' ' 127.0.0.0', '') MOOSHAK_SERVER = 'mooshak_server' class MultiPartForm(object): """Accumulate the data to be used when posting a form.""" def __init__(self): self.form_fields = [] self.files = [] self.boundary = mimetools.choose_boundary() return def get_content_type(self): return 'multipart/form-data; boundary=%s' % self.boundary
from common import utils as common_utils from models import config from models import models from models import roles from models import transforms from models import messages as models_messages from controllers import utils from modules.explorer import constants from modules.explorer import messages from modules.drive import dashboard_handler from modules.oeditor import oeditor GCB_ENABLE_COURSE_EXPLORER_PAGE = config.ConfigProperty( 'gcb_enable_course_explorer_page', bool, messages.SITE_SETTINGS_COURSE_EXPLORER, default_value=False, label='Course Explorer', multiline=False, validator=None) COURSE_EXPLORER_SETTINGS = config.ConfigProperty('course_explorer_settings', str, '', label='Site settings', show_in_site_settings=False) def make_logo_url(mime_type, bytes_base64): return 'data:{};base64,{}'.format(mime_type, bytes_base64)
# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Settings for the Guide module.""" __author__ = [ '[email protected] (Davy Risso)', ] from models import config from modules.guide import messages GCB_ENABLE_GUIDE_PAGE = config.ConfigProperty('gcb_enable_guide_page', bool, messages.SITE_SETTINGS_GUIDE, default_value=True, label='Guide', multiline=False, validator=None) namespaced_routes = []