Commit fbada1a9 by Piotr Mitros

Merge branch 'master' into pmitros/modular-refactor

parents 32c50090 d7d831b4
......@@ -13,4 +13,9 @@ db.newaskbot
db.oldaskbot
flushdb.sh
build
.coverage
coverage.xml
cover/
log/
reports/
\#*\#
\ No newline at end of file
source :rubygems
gem 'guard', '~> 1.0.3'
gem 'guard-process', '~> 1.0.3'
gem 'guard-coffeescript', '~> 0.6.0'
gem 'rake', '0.8.3'
gem 'sass', '3.1.15'
gem 'guard-sass', :github => 'sikachu/guard-sass'
gem 'bourbon', '~> 1.3.6'
gem 'libnotify', '~> 0.7.2'
gem 'ruby_gntp', '~> 0.3.4'
GIT
remote: git://github.com/sikachu/guard-sass.git
revision: 2a646996d7fdaa2fabf5f65ba700bd8b02f14c1b
specs:
guard-sass (0.6.0)
guard (>= 0.4.0)
sass (>= 3.1)
GEM
remote: http://rubygems.org/
specs:
bourbon (1.3.6)
sass (>= 3.1)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.3.3)
execjs (1.3.2)
multi_json (~> 1.0)
ffi (1.0.11)
guard (1.0.3)
ffi (>= 0.5.0)
thor (>= 0.14.6)
guard-coffeescript (0.6.0)
coffee-script (>= 2.2.0)
guard (>= 0.8.3)
guard-process (1.0.3)
ffi (~> 1.0.9)
guard (>= 0.4.2)
spoon (~> 0.0.1)
libnotify (0.7.2)
multi_json (1.3.5)
ruby_gntp (0.3.4)
rake (0.8.3)
sass (3.1.15)
spoon (0.0.1)
thor (0.15.2)
PLATFORMS
ruby
DEPENDENCIES
bourbon (~> 1.3.6)
guard (~> 1.0.3)
guard-coffeescript (~> 0.6.0)
guard-process (~> 1.0.3)
guard-sass!
libnotify (~> 0.7.2)
ruby_gntp (~> 0.3.4)
rake (= 0.8.3)
sass (= 3.1.15)
require 'bourbon'
# Helper method
def production?
@@options[:group].include? 'production'
end
guard :coffeescript, :name => :jasmine, :input => 'templates/coffee/spec', :all_on_start => production?
guard :coffeescript, :input => 'templates/coffee/src', :noop => true
guard :process, :name => :coffeescript, :command => "coffee -j static/js/application.js -c templates/coffee/src" do
watch(%r{^templates/coffee/src/(.+)\.coffee$})
end
if production?
guard :sass, :input => 'templates/sass', :output => 'static/css', :style => :compressed, :all_on_start => true
else
guard :sass, :input => 'templates/sass', :output => 'static/css', :style => :nested, :line_numbers => true
end
#! /bin/bash
cd $(dirname $0) && django-admin.py collectstatic --noinput --settings=envs.aws --pythonpath=.
def contextualize_text(text, context): # private
''' Takes a string with variables. E.g. $a+$b.
Does a substitution of those variables from the context '''
if not text: return text
for key in sorted(context, lambda x,y:cmp(len(y),len(x))):
text=text.replace('$'+key, str(context[key]))
return text
......@@ -7,7 +7,6 @@ Does some caching (to be explained).
'''
import hashlib
import logging
import os
import re
......@@ -16,6 +15,7 @@ import urllib
from datetime import timedelta
from lxml import etree
from util.memcache import fasthash
try: # This lets us do __name__ == ='__main__'
from django.conf import settings
......@@ -57,10 +57,6 @@ def parse_timedelta(time_str):
time_params[name] = int(param)
return timedelta(**time_params)
def fasthash(string):
m = hashlib.new("md4")
m.update(string)
return "id"+m.hexdigest()
def xpath(xml, query_string, **args):
''' Safe xpath query into an xml tree:
......@@ -122,7 +118,7 @@ def id_tag(course):
new_id = default_ids[elem.tag] + new_id
elem.set('id', new_id)
else:
elem.set('id', fasthash(etree.tostring(elem)))
elem.set('id', "id"+fasthash(etree.tostring(elem)))
def propogate_downward_tag(element, attribute_name, parent_attribute = None):
''' This call is to pass down an attribute to all children. If an element
......@@ -160,11 +156,11 @@ def user_groups(user):
cache_expiration = 60 * 60 # one hour
# Kill caching on dev machines -- we switch groups a lot
group_names = cache.get(fasthash(key))
group_names = cache.get(key)
if group_names is None:
group_names = [u.name for u in UserTestGroup.objects.filter(users=user)]
cache.set(fasthash(key), group_names, cache_expiration)
cache.set(key, group_names, cache_expiration)
return group_names
......@@ -203,7 +199,7 @@ def course_file(user,coursename=None):
cache_key = filename + "_processed?dev_content:" + str(options['dev_content']) + "&groups:" + str(sorted(groups))
if "dev" not in settings.DEFAULT_GROUPS:
tree_string = cache.get(fasthash(cache_key))
tree_string = cache.get(cache_key)
else:
tree_string = None
......@@ -211,7 +207,7 @@ def course_file(user,coursename=None):
tree = course_xml_process(etree.XML(render_to_string(filename, options, namespace = 'course')))
tree_string = etree.tostring(tree)
cache.set(fasthash(cache_key), tree_string, 60)
cache.set(cache_key, tree_string, 60)
else:
tree = etree.XML(tree_string)
......
......@@ -13,8 +13,8 @@ from fs.osfs import OSFS
from django.conf import settings
from mitxmako.shortcuts import render_to_string
from models import StudentModule
from multicourse import multicourse_settings
import courseware.modules
......@@ -31,6 +31,8 @@ class I4xSystem(object):
self.track_function = track_function
if not filestore:
self.filestore = OSFS(settings.DATA_DIR)
else:
self.filestore = filestore
self.render_function = render_function
self.exception404 = Http404
def __repr__(self):
......@@ -95,15 +97,21 @@ def render_x_module(user, request, xml_module, module_object_preload):
state = smod.state
# get coursename if stored
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
coursename = multicourse_settings.get_coursename_from_request(request)
if coursename and settings.ENABLE_MULTICOURSE:
xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
data_root = settings.DATA_DIR + xp
else:
data_root = settings.DATA_DIR
# Create a new instance
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module_type+'/'+module_id+'/'
system = I4xSystem(track_function = make_track_function(request),
render_function = lambda x: render_module(user, request, x, module_object_preload),
ajax_url = ajax_url,
filestore = None
filestore = OSFS(data_root),
)
instance=module_class(system,
etree.tostring(xml_module),
......
......@@ -2,6 +2,8 @@ import logging
import urllib
import json
from fs.osfs import OSFS
from django.conf import settings
from django.core.context_processors import csrf
from django.contrib.auth.models import User
......@@ -17,7 +19,6 @@ from lxml import etree
from module_render import render_module, make_track_function, I4xSystem
from models import StudentModule
from student.models import UserProfile
from util.errors import record_exception
from util.views import accepts
from multicourse import multicourse_settings
......@@ -38,9 +39,7 @@ def gradebook(request):
if 'course_admin' not in content_parser.user_groups(request.user):
raise Http404
# TODO: This should be abstracted out. We repeat this logic many times.
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
coursename = multicourse_settings.get_coursename_from_request(request)
student_objects = User.objects.all()[:100]
student_info = [{'username' :s.username,
......@@ -68,8 +67,7 @@ def profile(request, student_id = None):
user_info = UserProfile.objects.get(user=student) # request.user.profile_cache #
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
coursename = multicourse_settings.get_coursename_from_request(request)
context={'name':user_info.name,
'username':student.username,
......@@ -110,13 +108,12 @@ def render_section(request, section):
if not settings.COURSEWARE_ENABLED:
return redirect('/')
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
coursename = multicourse_settings.get_coursename_from_request(request)
try:
dom = content_parser.section_file(user, section, coursename)
except:
record_exception(log, "Unable to parse courseware xml")
log.exception("Unable to parse courseware xml")
return render_to_response('courseware-error.html', {})
context = {
......@@ -135,7 +132,7 @@ def render_section(request, section):
try:
module = render_module(user, request, dom, module_object_preload)
except:
record_exception(log, "Unable to load module")
log.exception("Unable to load module")
context.update({
'init': '',
'content': render_to_string("module-error.html", {}),
......@@ -184,7 +181,7 @@ def index(request, course=None, chapter="Using the System", section="Hints"):
try:
dom = content_parser.course_file(user,course) # also pass course to it, for course-specific XML path
except:
record_exception(log, "Unable to parse courseware xml")
log.exception("Unable to parse courseware xml")
return render_to_response('courseware-error.html', {})
dom_module = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]/*[1]",
......@@ -213,7 +210,7 @@ def index(request, course=None, chapter="Using the System", section="Hints"):
try:
module = render_module(user, request, module, module_object_preload)
except:
record_exception(log, "Unable to load module")
log.exception("Unable to load module")
context.update({
'init': '',
'content': render_to_string("module-error.html", {}),
......@@ -251,14 +248,19 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/'
# get coursename if stored
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
coursename = multicourse_settings.get_coursename_from_request(request)
if coursename and settings.ENABLE_MULTICOURSE:
xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
data_root = settings.DATA_DIR + xp
else:
data_root = settings.DATA_DIR
# Grab the XML corresponding to the request from course.xml
try:
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
except:
record_exception(log, "Unable to load module during ajax call")
log.exception("Unable to load module during ajax call")
if accepts(request, 'text/html'):
return render_to_response("module-error.html", {})
else:
......@@ -269,7 +271,7 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
system = I4xSystem(track_function = make_track_function(request),
render_function = None,
ajax_url = ajax_url,
filestore = None
filestore = OSFS(data_root),
)
try:
......@@ -278,7 +280,7 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
id,
state=oldstate)
except:
record_exception(log, "Unable to load module instance during ajax call")
log.exception("Unable to load module instance during ajax call")
if accepts(request, 'text/html'):
return render_to_response("module-error.html", {})
else:
......@@ -307,12 +309,12 @@ def quickedit(request, id=None):
print "In deployed use, this will only edit on one server"
print "We need a setting to disable for production where there is"
print "a load balanacer"
if not request.user.is_staff():
if not request.user.is_staff:
return redirect('/')
# get coursename if stored
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
coursename = multicourse_settings.get_coursename_from_request(request)
xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
def get_lcp(coursename,id):
# Grab the XML corresponding to the request from course.xml
......@@ -325,9 +327,8 @@ def quickedit(request, id=None):
system = I4xSystem(track_function = make_track_function(request),
render_function = None,
ajax_url = ajax_url,
filestore = None,
coursename = coursename,
role = 'staff' if request.user.is_staff else 'student', # TODO: generalize this
filestore = OSFS(settings.DATA_DIR + xp),
#role = 'staff' if request.user.is_staff else 'student', # TODO: generalize this
)
instance=courseware.modules.get_module_class(module)(system,
xml,
......
from django.conf.urls.defaults import *
urlpatterns = patterns('',
url(r'^$', 'heartbeat.views.heartbeat', name='heartbeat'),
)
import json
from datetime import datetime
from django.http import HttpResponse
def heartbeat(request):
"""
Simple view that a loadbalancer can check to verify that the app is up
"""
output = {
'date': datetime.now().isoformat()
}
return HttpResponse(json.dumps(output, indent=4))
......@@ -42,6 +42,11 @@ else: # default to 6.002_Spring_2012
#-----------------------------------------------------------------------------
# wrapper functions around course settings
def get_coursename_from_request(request):
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
return coursename
def get_course_settings(coursename):
if not coursename:
if hasattr(settings,'COURSE_DEFAULT'):
......
# multicourse/views.py
import datetime
import json
import sys
from django.conf import settings
from django.contrib.auth.models import User
from django.core.context_processors import csrf
from django.core.mail import send_mail
from django.http import Http404
from django.http import HttpResponse
from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string
import courseware.capa.calc
import track.views
from multicourse import multicourse_settings
def mitxhome(request):
''' Home page (link from main header). List of courses. '''
if settings.ENABLE_MULTICOURSE:
context = {'courseinfo' : multicourse_settings.COURSE_SETTINGS}
return render_to_response("mitxhome.html", context)
return info(request)
......@@ -153,13 +153,16 @@ MANAGERS = ADMINS
# Static content
STATIC_URL = '/static/'
ADMIN_MEDIA_PREFIX = '/static/admin/'
STATIC_ROOT = ENV_ROOT / "staticfiles" # We don't run collectstatic -- this is to appease askbot checks
STATIC_ROOT = ENV_ROOT / "staticfiles"
# FIXME: We should iterate through the courses we have, adding the static
# contents for each of them. (Right now we just use symlinks.)
STATICFILES_DIRS = [
PROJECT_ROOT / "static",
ASKBOT_ROOT / "askbot" / "skins",
("circuits", DATA_DIR / "images"),
("handouts", DATA_DIR / "handouts"),
("subs", DATA_DIR / "subs"),
# This is how you would use the textbook images locally
# ("book", ENV_ROOT / "book_images")
......@@ -214,14 +217,14 @@ SIMPLE_WIKI_REQUIRE_LOGIN_EDIT = True
SIMPLE_WIKI_REQUIRE_LOGIN_VIEW = False
################################# Jasmine ###################################
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/templates/coffee'
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
################################# Middleware ###################################
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'staticfiles.finders.FileSystemFinder',
'staticfiles.finders.AppDirectoriesFinder',
)
# List of callables that know how to import templates from various sources.
......@@ -257,6 +260,62 @@ MIDDLEWARE_CLASSES = (
# 'debug_toolbar.middleware.DebugToolbarMiddleware',
)
############################### Pipeline #######################################
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
PIPELINE_CSS = {
'application': {
'source_filenames': ['sass/application.scss'],
'output_filename': 'css/application.css',
},
'marketing': {
'source_filenames': ['sass/marketing.scss'],
'output_filename': 'css/marketing.css',
},
'marketing-ie': {
'source_filenames': ['sass/marketing-ie.scss'],
'output_filename': 'css/marketing-ie.css',
},
'print': {
'source_filenames': ['sass/print.scss'],
'output_filename': 'css/print.css',
}
}
PIPELINE_JS = {
'application': {
'source_filenames': [
'coffee/src/calculator.coffee',
'coffee/src/courseware.coffee',
'coffee/src/feedback_form.coffee',
'coffee/src/main.coffee'
],
'output_filename': 'js/application.js'
}
}
PIPELINE_COMPILERS = [
'pipeline.compilers.sass.SASSCompiler',
'pipeline.compilers.coffee.CoffeeScriptCompiler',
]
PIPELINE_SASS_ARGUMENTS = '-t compressed -r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
PIPELINE_CSS_COMPRESSOR = None
PIPELINE_JS_COMPRESSOR = 'pipeline.compressors.yui.YUICompressor'
STATICFILES_IGNORE_PATTERNS = (
"sass/*",
"coffee/*",
"*.py",
"*.pyc"
)
PIPELINE_YUI_BINARY = 'yui-compressor'
PIPELINE_SASS_BINARY = 'sass'
PIPELINE_COFFEE_SCRIPT_BINARY = 'coffee'
################################### APPS #######################################
INSTALLED_APPS = (
# Standard ones that are always installed...
......@@ -266,9 +325,12 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.staticfiles',
'south',
# For asset pipelining
'pipeline',
'staticfiles',
# Our courseware
'circuit',
'courseware',
......
......@@ -31,7 +31,8 @@ CACHES = {
# In staging/prod envs, the sessions also live here.
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'mitx_loc_mem_cache'
'LOCATION': 'mitx_loc_mem_cache',
'KEY_FUNCTION': 'util.memcache.safe_key',
},
# The general cache is what you get if you use our util.cache. It's used for
......@@ -43,6 +44,7 @@ CACHES = {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
'KEY_PREFIX': 'general',
'VERSION': 4,
'KEY_FUNCTION': 'util.cache.memcache_safe_key',
}
}
......@@ -81,3 +83,7 @@ FILE_UPLOAD_HANDLERS = (
'django.core.files.uploadhandler.MemoryFileUploadHandler',
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
)
########################### PIPELINE #################################
PIPELINE_SASS_ARGUMENTS = '-r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
......@@ -30,12 +30,14 @@ CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
'KEY_FUNCTION': 'util.memcache.safe_key',
},
'general': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
'KEY_PREFIX' : 'general',
'VERSION' : 5,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
}
......
......@@ -29,7 +29,7 @@ def get_logger_config(log_dir,
" %(process)d] [%(filename)s:%(lineno)d] - %(message)s").format(
logging_env=logging_env, hostname=hostname)
handlers = ['console'] if debug else ['console', 'syslogger']
handlers = ['console'] if debug else ['console', 'syslogger', 'newrelic']
return {
'version': 1,
......@@ -60,6 +60,11 @@ def get_logger_config(log_dir,
'filename' : tracking_file_loc,
'formatter' : 'raw',
},
'newrelic' : {
'level': 'ERROR',
'class': 'newrelic_logging.NewRelicHandler',
'formatter': 'raw',
}
},
'loggers' : {
'django' : {
......@@ -82,5 +87,10 @@ def get_logger_config(log_dir,
'level' : 'DEBUG',
'propagate' : False
},
'keyedcache' : {
'handlers' : handlers,
'level' : 'DEBUG',
'propagate' : False
},
}
}
\ No newline at end of file
}
......@@ -30,7 +30,8 @@ CACHES = {
# In staging/prod envs, the sessions also live here.
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'mitx_loc_mem_cache'
'LOCATION': 'mitx_loc_mem_cache',
'KEY_FUNCTION': 'util.memcache.safe_key',
},
# The general cache is what you get if you use our util.cache. It's used for
......@@ -42,6 +43,7 @@ CACHES = {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
'KEY_PREFIX': 'general',
'VERSION': 4,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
}
......
......@@ -54,7 +54,8 @@ CACHES = {
# In staging/prod envs, the sessions also live here.
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'mitx_loc_mem_cache'
'LOCATION': 'mitx_loc_mem_cache',
'KEY_FUNCTION': 'util.memcache.safe_key',
},
# The general cache is what you get if you use our util.cache. It's used for
......@@ -66,6 +67,7 @@ CACHES = {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
'KEY_PREFIX': 'general',
'VERSION': 4,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
}
......
import newrelic.agent
import logging
class NewRelicHandler(logging.Handler):
def emit(self, record):
if record.exc_info is not None:
params = record.__dict__
params['log_message'] = record.getMessage()
newrelic.agent.record_exception(
*record.exc_info,
params=params
)
from staticfiles.storage import staticfiles_storage
from mitxmako.shortcuts import render_to_string
from pipeline.conf import settings
from pipeline.packager import Packager
from pipeline.utils import guess_type
def compressed_css(package_name):
package = settings.PIPELINE_CSS.get(package_name, {})
if package:
package = {package_name: package}
packager = Packager(css_packages=package, js_packages={})
package = packager.package_for('css', package_name)
if settings.PIPELINE:
return render_css(package, package.output_filename)
else:
paths = packager.compile(package.paths)
return render_individual_css(package, paths)
def render_css(package, path):
template_name = package.template_name or "pipeline_mako/css.html"
context = package.extra_context
context.update({
'type': guess_type(path, 'text/css'),
'url': staticfiles_storage.url(path)
})
return render_to_string(template_name, context)
def render_individual_css(package, paths):
tags = [render_css(package, path) for path in paths]
return '\n'.join(tags)
def compressed_js(package_name):
package = settings.PIPELINE_JS.get(package_name, {})
if package:
package = {package_name: package}
packager = Packager(css_packages={}, js_packages=package)
package = packager.package_for('js', package_name)
if settings.PIPELINE:
return render_js(package, package.output_filename)
else:
paths = packager.compile(package.paths)
templates = packager.pack_templates(package)
return render_individual_js(package, paths, templates)
def render_js(package, path):
template_name = package.template_name or "pipeline_mako/js.html"
context = package.extra_context
context.update({
'type': guess_type(path, 'text/javascript'),
'url': staticfiles_storage.url(path)
})
return render_to_string(template_name, context)
def render_inline_js(package, js):
context = package.extra_context
context.update({
'source': js
})
return render_to_string("pipeline_mako/inline_js.html", context)
def render_individual_js(package, paths, templates=None):
tags = [render_js(package, js) for js in paths]
if templates:
tags.append(render_inline_js(package, templates))
return '\n'.join(tags)
from staticfiles.storage import staticfiles_storage
import re
PREFIX = '/static/'
STATIC_PATTERN = re.compile(r"""
(?P<quote>['"]) # the opening quotes
{prefix} # the prefix
(?P<rest>.*?) # everything else in the url
(?P=quote) # the first matching closing quote
""".format(prefix=PREFIX), re.VERBOSE)
PREFIX_LEN = len(PREFIX)
def replace(static_url):
quote = static_url.group('quote')
url = staticfiles_storage.url(static_url.group('rest'))
return "".join([quote, url, quote])
def replace_urls(text):
return STATIC_PATTERN.sub(replace, text)
import newrelic.agent
import sys
def record_exception(logger, msg, params={}, ignore_errors=[]):
logger.exception(msg)
newrelic.agent.record_exception(*sys.exc_info())
"""
This module provides a KEY_FUNCTION suitable for use with a memcache backend
so that we can cache any keys, not just ones that memcache would ordinarily accept
"""
from django.utils.encoding import smart_str
import hashlib
import urllib
def fasthash(string):
m = hashlib.new("md4")
m.update(string)
return m.hexdigest()
def safe_key(key, key_prefix, version):
safe_key = urllib.quote_plus(smart_str(key))
if len(safe_key) > 250:
safe_key = fasthash(safe_key)
return ":".join([key_prefix, str(version), safe_key])
......@@ -61,12 +61,6 @@ def info(request):
''' Info page (link from main header) '''
return render_to_response("info.html", {})
def mitxhome(request):
''' Home page (link from main header). List of courses. '''
if settings.ENABLE_MULTICOURSE:
return render_to_response("mitxhome.html", {})
return info(request)
# From http://djangosnippets.org/snippets/1042/
def parse_accept_header(accept):
"""Parse the Accept header *accept*, returning a list with pairs of
......
......@@ -38,10 +38,12 @@ task :default => [:pep8, :pylint, :test]
directory REPORT_DIR
desc "Run pep8 on all of djangoapps"
task :pep8 => REPORT_DIR do
sh("pep8 --ignore=E501 djangoapps | tee #{REPORT_DIR}/pep8.report")
end
desc "Run pylint on all of djangoapps"
task :pylint => REPORT_DIR do
Dir.chdir("djangoapps") do
Dir["*"].each do |app|
......@@ -50,12 +52,20 @@ task :pylint => REPORT_DIR do
end
end
desc "Run all django tests on our djangoapps"
task :test => REPORT_DIR do
ENV['NOSE_XUNIT_FILE'] = File.join(REPORT_DIR, "nosetests.xml")
django_admin = ENV['DJANGO_ADMIN_PATH'] || select_executable('django-admin.py', 'django-admin')
sh("#{django_admin} test --settings=envs.test --pythonpath=. $(ls djangoapps)")
end
desc "Start a local server with the specified environment (defaults to dev). Other useful environments are devplus (for dev testing with a real local database)"
task :runserver, [:env] => [] do |t, args|
args.with_defaults(:env => 'dev')
django_admin = ENV['DJANGO_ADMIN_PATH'] || select_executable('django-admin.py', 'django-admin')
sh("#{django_admin} runserver --settings=envs.#{args.env} --pythonpath=.")
end
task :package do
FileUtils.mkdir_p(BUILD_DIR)
......@@ -68,24 +78,32 @@ task :package do
set -x
chown -R makeitso:makeitso #{INSTALL_DIR_PATH}
chmod +x #{INSTALL_DIR_PATH}/collect_static_resources
service gunicorn stop
rm -f #{LINK_PATH}
ln -s #{INSTALL_DIR_PATH} #{LINK_PATH}
chown makeitso:makeitso #{LINK_PATH}
/opt/wwc/mitx/collect_static_resources
# Delete mako temp files
rm -rf /tmp/tmp*mako
service gunicorn start
POSTINSTALL
postinstall.close()
FileUtils.chmod(0755, postinstall.path)
args = ["fakeroot", "fpm", "-s", "dir", "-t", "deb",
"--verbose",
"--after-install=#{postinstall.path}",
"--prefix=#{INSTALL_DIR_PATH}",
"--exclude=build",
"--exclude=rakefile",
"--exclude=.git",
"--exclude=**/build/**",
"--exclude=**/rakefile",
"--exclude=**/.git/**",
"--exclude=**/*.pyc",
"--exclude=reports",
"--exclude=**/reports/**",
"-C", "#{REPO_ROOT}",
"--provides=#{PACKAGE_NAME}",
"--name=#{NORMALIZED_DEPLOY_NAME}",
......
django-admin.py runserver --settings=envs.dev --pythonpath=.
../../book_images/
\ No newline at end of file
../../data/images/
\ No newline at end of file
......@@ -34,15 +34,10 @@ Conveniently, you can install Node via `apt-get`, then use npm:
Compiling
---------
We're using Guard to watch your folder and automatic compile those CoffeeScript
files. First, install guard by using Bundler:
$ gem install bundler
$ bundle install
Then you can run this command:
$ bundle exec guard
The dev server will automatically compile coffeescript files that have changed.
Simply start the server using:
$ rake runserver
Testing
-------
......
......@@ -10,6 +10,11 @@ class window.Calculator
toggle: ->
$('li.calc-main').toggleClass 'open'
$('#calculator_wrapper #calculator_input').focus()
if $('.calc.closed').length
$('.calc').attr 'aria-label', 'Open Calculator'
else
$('.calc').attr 'aria-label', 'Close Calculator'
$('.calc').toggleClass 'closed'
helpToggle: ->
......
../../askbot-devel/askbot/skins/common/
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
.CodeMirror {
line-height: 1em;
font-family: monospace;
}
.CodeMirror-scroll {
overflow: auto;
height: 300px;
/* This is needed to prevent an IE[67] bug where the scrolled content
is visible outside of the scrolling box. */
position: relative;
outline: none;
}
.CodeMirror-gutter {
position: absolute; left: 0; top: 0;
z-index: 10;
background-color: #f7f7f7;
border-right: 1px solid #eee;
min-width: 2em;
height: 100%;
}
.CodeMirror-gutter-text {
color: #aaa;
text-align: right;
padding: .4em .2em .4em .4em;
white-space: pre !important;
}
.CodeMirror-lines {
padding: .4em;
white-space: pre;
}
.CodeMirror pre {
-moz-border-radius: 0;
-webkit-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
border-width: 0; margin: 0; padding: 0; background: transparent;
font-family: inherit;
font-size: inherit;
padding: 0; margin: 0;
white-space: pre;
word-wrap: normal;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror textarea {
outline: none !important;
}
.CodeMirror pre.CodeMirror-cursor {
z-index: 10;
position: absolute;
visibility: hidden;
border-left: 1px solid black;
border-right:none;
width:0;
}
.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
.CodeMirror-focused pre.CodeMirror-cursor {
visibility: visible;
}
div.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* Default theme */
.cm-s-default span.cm-keyword {color: #708;}
.cm-s-default span.cm-atom {color: #219;}
.cm-s-default span.cm-number {color: #164;}
.cm-s-default span.cm-def {color: #00f;}
.cm-s-default span.cm-variable {color: black;}
.cm-s-default span.cm-variable-2 {color: #05a;}
.cm-s-default span.cm-variable-3 {color: #085;}
.cm-s-default span.cm-property {color: black;}
.cm-s-default span.cm-operator {color: black;}
.cm-s-default span.cm-comment {color: #a50;}
.cm-s-default span.cm-string {color: #a11;}
.cm-s-default span.cm-string-2 {color: #f50;}
.cm-s-default span.cm-meta {color: #555;}
.cm-s-default span.cm-error {color: #f00;}
.cm-s-default span.cm-qualifier {color: #555;}
.cm-s-default span.cm-builtin {color: #30a;}
.cm-s-default span.cm-bracket {color: #cc7;}
.cm-s-default span.cm-tag {color: #170;}
.cm-s-default span.cm-attribute {color: #00c;}
.cm-s-default span.cm-header {color: #a0a;}
.cm-s-default span.cm-quote {color: #090;}
.cm-s-default span.cm-hr {color: #999;}
.cm-s-default span.cm-link {color: #00c;}
span.cm-header, span.cm-strong {font-weight: bold;}
span.cm-em {font-style: italic;}
span.cm-emstrong {font-style: italic; font-weight: bold;}
span.cm-link {text-decoration: underline;}
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
body{margin:0;padding:0}.wrapper,.subpage,section.copyright,section.tos,section.privacy-policy,section.honor-code,header.announcement div,section.index-content,footer{margin:0;overflow:hidden}div#enroll form{display:none}
../../askbot-devel/askbot/skins/default/
\ No newline at end of file
../../data/handouts/
\ No newline at end of file
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