diff --git a/courseware/capa/calc.py b/courseware/capa/calc.py
index 41ced03e58..63c5c9de01 100644
--- a/courseware/capa/calc.py
+++ b/courseware/capa/calc.py
@@ -1,14 +1,17 @@
import copy
+import logging
import math
import operator
+import re
import numpy
+import scipy.constants
from pyparsing import Word, alphas, nums, oneOf, Literal
from pyparsing import ZeroOrMore, OneOrMore, StringStart
from pyparsing import StringEnd, Optional, Forward
from pyparsing import CaselessLiteral, Group, StringEnd
-from pyparsing import NoMatch, stringEnd
+from pyparsing import NoMatch, stringEnd, alphanums
default_functions = {'sin' : numpy.sin,
'cos' : numpy.cos,
@@ -23,19 +26,75 @@ default_functions = {'sin' : numpy.sin,
'abs':numpy.abs
}
default_variables = {'j':numpy.complex(0,1),
- 'e':numpy.complex(numpy.e)
+ 'e':numpy.e,
+ 'pi':numpy.pi,
+ 'k':scipy.constants.k,
+ 'c':scipy.constants.c,
+ 'T':298.15,
+ 'q':scipy.constants.e
}
+log = logging.getLogger("mitx.courseware.capa")
-def evaluator(variables, functions, string):
+class UndefinedVariable(Exception):
+ def raiseself(self):
+ ''' Helper so we can use inside of a lambda '''
+ raise self
+
+
+general_whitespace = re.compile('[^\w]+')
+def check_variables(string, variables):
+ ''' Confirm the only variables in string are defined.
+
+ Pyparsing uses a left-to-right parser, which makes the more
+ elegant approach pretty hopeless.
+
+ achar = reduce(lambda a,b:a|b ,map(Literal,alphas)) # Any alphabetic character
+ undefined_variable = achar + Word(alphanums)
+ undefined_variable.setParseAction(lambda x:UndefinedVariable("".join(x)).raiseself())
+ varnames = varnames | undefined_variable'''
+ possible_variables = re.split(general_whitespace, string) # List of all alnums in string
+ bad_variables = list()
+ for v in possible_variables:
+ if len(v) == 0:
+ continue
+ if v[0] <= '9' and '0' <= 'v': # Skip things that begin with numbers
+ continue
+ if v not in variables:
+ bad_variables.append(v)
+ if len(bad_variables)>0:
+ raise UndefinedVariable(' '.join(bad_variables))
+
+def evaluator(variables, functions, string, cs=False):
''' Evaluate an expression. Variables are passed as a dictionary
from string to value. Unary functions are passed as a dictionary
- from string to function '''
+ from string to function. Variables must be floats.
+ cs: Case sensitive
+
+ TODO: Fix it so we can pass integers and complex numbers in variables dict
+ '''
+ # log.debug("variables: {0}".format(variables))
+ # log.debug("functions: {0}".format(functions))
+ # log.debug("string: {0}".format(string))
+
all_variables = copy.copy(default_variables)
all_variables.update(variables)
all_functions = copy.copy(default_functions)
all_functions.update(functions)
+ if not cs:
+ string_cs = string.lower()
+ for v in all_variables.keys():
+ all_variables[v.lower()]=all_variables[v]
+ for f in all_functions.keys():
+ all_functions[f.lower()]=all_functions[f]
+ CasedLiteral = CaselessLiteral
+ else:
+ string_cs = string
+ CasedLiteral = Literal
+
+ check_variables(string_cs, set(all_variables.keys()+all_functions.keys()))
+
if string.strip() == "":
return float('nan')
ops = { "^" : operator.pow,
@@ -119,19 +178,22 @@ def evaluator(variables, functions, string):
# Handle variables passed in. E.g. if we have {'R':0.5}, we make the substitution.
# Special case for no variables because of how we understand PyParsing is put together
if len(all_variables)>0:
- varnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), all_variables.keys()))
+ # We sort the list so that var names (like "e2") match before
+ # mathematical constants (like "e"). This is kind of a hack.
+ all_variables_keys = sorted(all_variables.keys(), key=len, reverse=True)
+ varnames = sreduce(lambda x,y:x|y, map(lambda x: CasedLiteral(x), all_variables_keys))
varnames.setParseAction(lambda x:map(lambda y:all_variables[y], x))
else:
varnames=NoMatch()
# Same thing for functions.
if len(all_functions)>0:
- funcnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), all_functions.keys()))
+ funcnames = sreduce(lambda x,y:x|y, map(lambda x: CasedLiteral(x), all_functions.keys()))
function = funcnames+lpar.suppress()+expr+rpar.suppress()
function.setParseAction(func_parse_action)
else:
function = NoMatch()
- atom = number | varnames | lpar+expr+rpar | function
+ atom = number | function | varnames | lpar+expr+rpar
factor << (atom + ZeroOrMore(exp+atom)).setParseAction(exp_parse_action) # 7^6
paritem = factor + ZeroOrMore(Literal('||')+factor) # 5k || 4k
paritem=paritem.setParseAction(parallel)
@@ -147,7 +209,9 @@ if __name__=='__main__':
print "X",evaluator(variables, functions, "10000||sin(7+5)-6k")
print "X",evaluator(variables, functions, "13")
print evaluator({'R1': 2.0, 'R3':4.0}, {}, "13")
- #
+
+ print evaluator({'e1':1,'e2':1.0,'R3':7,'V0':5,'R5':15,'I1':1,'R4':6}, {},"e2")
+
print evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5")
print evaluator({},{}, "-1")
print evaluator({},{}, "-(7+5)")
diff --git a/courseware/capa/capa_problem.py b/courseware/capa/capa_problem.py
index 73ef168cf0..c5a81e8100 100644
--- a/courseware/capa/capa_problem.py
+++ b/courseware/capa/capa_problem.py
@@ -15,7 +15,7 @@ from mako.template import Template
from util import contextualize_text
from inputtypes import textline, schematic
-from responsetypes import numericalresponse, formularesponse, customresponse, schematicresponse
+from responsetypes import numericalresponse, formularesponse, customresponse, schematicresponse, StudentInputError
import calc
import eia
@@ -53,7 +53,7 @@ html_special_response = {"textline":textline.render,
"schematic":schematic.render}
class LoncapaProblem(object):
- def __init__(self, filename, id=None, state=None):
+ def __init__(self, filename, id=None, state=None, seed=None):
## Initialize class variables from state
self.seed = None
self.student_answers = dict()
@@ -61,6 +61,9 @@ class LoncapaProblem(object):
self.done = False
self.filename = filename
+ if seed != None:
+ self.seed = seed
+
if id:
self.problem_id = id
else:
@@ -78,10 +81,14 @@ class LoncapaProblem(object):
if 'done' in state:
self.done = state['done']
+# print self.seed
+
# TODO: Does this deplete the Linux entropy pool? Is this fast enough?
if not self.seed:
self.seed=struct.unpack('i', os.urandom(4))[0]
+# print filename, self.seed, seed
+
## Parse XML file
#log.debug(u"LoncapaProblem() opening file {0}".format(filename))
file_text = open(filename).read()
diff --git a/courseware/capa/responsetypes.py b/courseware/capa/responsetypes.py
index 93ebac85f9..8809d935b1 100644
--- a/courseware/capa/responsetypes.py
+++ b/courseware/capa/responsetypes.py
@@ -3,8 +3,9 @@ import math
import numpy
import random
import scipy
+import traceback
-from calc import evaluator
+from calc import evaluator, UndefinedVariable
from django.conf import settings
from util import contextualize_text
@@ -19,24 +20,36 @@ global_context={'random':random,
'calc':calc,
'eia':eia}
+
+def compare_with_tolerance(v1, v2, tol):
+ ''' Compare v1 to v2 with maximum tolerance tol
+ tol is relative if it ends in %; otherwise, it is absolute
+ '''
+ relative = "%" in tol
+ if relative:
+ tolerance_rel = evaluator(dict(),dict(),tol[:-1]) * 0.01
+ tolerance = tolerance_rel * max(abs(v1), abs(v2))
+ else:
+ tolerance = evaluator(dict(),dict(),tol)
+ return abs(v1-v2) <= tolerance
+
class numericalresponse(object):
def __init__(self, xml, context):
self.xml = xml
self.correct_answer = contextualize_text(xml.get('answer'), context)
self.correct_answer = float(self.correct_answer)
- self.tolerance = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default',
+ self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default',
id=xml.get('id'))[0]
- self.tolerance = contextualize_text(self.tolerance, context)
- self.tolerance = evaluator(dict(),dict(),self.tolerance)
+ self.tolerance = contextualize_text(self.tolerance_xml, context)
self.answer_id = xml.xpath('//*[@id=$id]//textline/@id',
id=xml.get('id'))[0]
def grade(self, student_answers):
''' Display HTML for a numeric response '''
student_answer = student_answers[self.answer_id]
- error = abs(evaluator(dict(),dict(),student_answer) - self.correct_answer)
- allowed_error = abs(self.correct_answer*self.tolerance)
- if error <= allowed_error:
+ correct = compare_with_tolerance (evaluator(dict(),dict(),student_answer), self.correct_answer, self.tolerance)
+
+ if correct:
return {self.answer_id:'correct'}
else:
return {self.answer_id:'incorrect'}
@@ -72,18 +85,31 @@ class customresponse(object):
# be handled by capa_problem
return {}
+class StudentInputError(Exception):
+ pass
+
class formularesponse(object):
def __init__(self, xml, context):
self.xml = xml
self.correct_answer = contextualize_text(xml.get('answer'), context)
self.samples = contextualize_text(xml.get('samples'), context)
- self.tolerance = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default',
+ self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default',
id=xml.get('id'))[0]
- self.tolerance = contextualize_text(self.tolerance, context)
- self.tolerance = evaluator(dict(),dict(),self.tolerance)
+ self.tolerance = contextualize_text(self.tolerance_xml, context)
self.answer_id = xml.xpath('//*[@id=$id]//textline/@id',
id=xml.get('id'))[0]
self.context = context
+ ts = xml.get('type')
+ if ts == None:
+ typeslist = []
+ else:
+ typeslist = ts.split(',')
+ if 'ci' in typeslist: # Case insensitive
+ self.case_sensitive = False
+ elif 'cs' in typeslist: # Case sensitive
+ self.case_sensitive = True
+ else: # Default
+ self.case_sensitive = False
def grade(self, student_answers):
@@ -102,10 +128,19 @@ class formularesponse(object):
instructor_variables[str(var)] = value
student_variables[str(var)] = value
instructor_result = evaluator(instructor_variables,dict(),self.correct_answer)
- student_result = evaluator(student_variables,dict(),student_answers[self.answer_id])
+ try:
+ #print student_variables,dict(),student_answers[self.answer_id]
+ student_result = evaluator(student_variables,dict(),
+ student_answers[self.answer_id],
+ cs = self.case_sensitive)
+ except UndefinedVariable as uv:
+ raise StudentInputError('Undefined: '+uv.message)
+ except:
+ #traceback.print_exc()
+ raise StudentInputError("Syntax Error")
if math.isnan(student_result) or math.isinf(student_result):
return {self.answer_id:"incorrect"}
- if abs( student_result - instructor_result ) > self.tolerance:
+ if not compare_with_tolerance(student_result, instructor_result, self.tolerance):
return {self.answer_id:"incorrect"}
return {self.answer_id:"correct"}
diff --git a/courseware/content_parser.py b/courseware/content_parser.py
index 8f6482ccf0..78d435557a 100644
--- a/courseware/content_parser.py
+++ b/courseware/content_parser.py
@@ -1,16 +1,17 @@
import hashlib
import json
import logging
+import os
import re
from datetime import timedelta
from lxml import etree
-from mako.template import Template
-from mako.lookup import TemplateLookup
try: # This lets us do __name__ == ='__main__'
from django.conf import settings
from student.models import UserProfile
+ from student.models import UserTestGroup
+ from mitxmako.shortcuts import render_to_response, render_to_string
except:
settings = None
@@ -49,7 +50,7 @@ def xpath(xml, query_string, **args):
We should remove this with the move to lxml.
We should also use lxml argument passing. '''
doc = etree.fromstring(xml)
- print type(doc)
+ #print type(doc)
def escape(x):
# TODO: This should escape the string. For now, we just assume it's made of valid characters.
# Couldn't figure out how to escape for lxml in a few quick Googles
@@ -60,7 +61,7 @@ def xpath(xml, query_string, **args):
return x
args=dict( ((k, escape(args[k])) for k in args) )
- print args
+ #print args
results = doc.xpath(query_string.format(**args))
return results
@@ -86,14 +87,20 @@ def item(l, default="", process=lambda x:x):
def id_tag(course):
''' Tag all course elements with unique IDs '''
- default_ids = {'video':'youtube',
+ old_ids = {'video':'youtube',
'problem':'filename',
'sequential':'id',
'html':'filename',
'vertical':'id',
'tab':'id',
- 'schematic':'id'}
-
+ 'schematic':'id',
+ 'book' : 'id'}
+ import courseware.modules
+ default_ids = courseware.modules.get_default_ids()
+
+ #print default_ids, old_ids
+ #print default_ids == old_ids
+
# Tag elements with unique IDs
elements = course.xpath("|".join(['//'+c for c in default_ids]))
for elem in elements:
@@ -135,23 +142,51 @@ def propogate_downward_tag(element, attribute_name, parent_attribute = None):
#to its children later.
return
-template_lookup = TemplateLookup(directories = [settings.DATA_DIR],
- module_directory = settings.MAKO_MODULE_DIR)
+def user_groups(user):
+ # TODO: Rewrite in Django
+ return [u.name for u in UserTestGroup.objects.raw("select * from auth_user, student_usertestgroup, student_usertestgroup_users where auth_user.id = student_usertestgroup_users.user_id and student_usertestgroup_users.usertestgroup_id = student_usertestgroup.id and auth_user.id = %s", [user.id])]
-def course_file(user):
- # TODO: Cache.
- filename = UserProfile.objects.get(user=user).courseware
- data_template = template_lookup.get_template(filename)
-
- options = {'dev_content':True}
-
- tree = etree.XML(data_template.render(**options))
+def course_xml_process(tree):
+ ''' Do basic pre-processing of an XML tree. Assign IDs to all
+ items without. Propagate due dates, grace periods, etc. to child
+ items.
+ '''
id_tag(tree)
propogate_downward_tag(tree, "due")
propogate_downward_tag(tree, "graded")
propogate_downward_tag(tree, "graceperiod")
return tree
+def course_file(user):
+ ''' Given a user, return course.xml
+ '''
+ # TODO: Cache.
+ filename = UserProfile.objects.get(user=user).courseware
+
+ groups = user_groups(user)
+
+ options = {'dev_content':settings.DEV_CONTENT,
+ 'groups' : groups}
+
+ tree = course_xml_process(etree.XML(render_to_string(filename, options, namespace = 'course')))
+ return tree
+
+def section_file(user, section):
+ ''' Given a user and the name of a section, return that section
+ '''
+ filename = section+".xml"
+
+ if filename not in os.listdir(settings.DATA_DIR + '/sections/'):
+ print filename+" not in "+str(os.listdir(settings.DATA_DIR + '/sections/'))
+ return None
+
+ options = {'dev_content':settings.DEV_CONTENT,
+ 'groups' : user_groups(user)}
+
+ tree = course_xml_process(etree.XML(render_to_string(filename, options, namespace = 'sections')))
+ return tree
+
+
def module_xml(coursefile, module, id_tag, module_id):
''' Get XML for a module based on module and module_id. Assumes
module occurs once in courseware XML file.. '''
diff --git a/courseware/management/commands/check_course.py b/courseware/management/commands/check_course.py
index 7ca8ef8242..4d0b9840ab 100644
--- a/courseware/management/commands/check_course.py
+++ b/courseware/management/commands/check_course.py
@@ -8,6 +8,7 @@ from django.contrib.auth.models import User
from mitx.courseware.content_parser import course_file
import mitx.courseware.module_render
+import mitx.courseware.modules
class Command(BaseCommand):
help = "Does basic validity tests on course.xml."
@@ -24,7 +25,7 @@ class Command(BaseCommand):
check = False
print "Confirming all modules render. Nothing should print during this step. "
for module in course.xpath('//problem|//html|//video|//vertical|//sequential|/tab'):
- module_class=mitx.courseware.module_render.modx_modules[module.tag]
+ module_class=mitx.courseware.modules.modx_modules[module.tag]
# TODO: Abstract this out in render_module.py
try:
instance=module_class(etree.tostring(module),
@@ -41,6 +42,7 @@ class Command(BaseCommand):
if os.path.exists(sections_dir):
print "Checking all section includes are valid XML"
for f in os.listdir(sections_dir):
+ print f
etree.parse(sections_dir+'/'+f)
else:
print "Skipping check of include files -- no section includes dir ("+sections_dir+")"
diff --git a/courseware/migrations/0003_done_grade_cache.py b/courseware/migrations/0003_done_grade_cache.py
new file mode 100644
index 0000000000..f2fedcf1a8
--- /dev/null
+++ b/courseware/migrations/0003_done_grade_cache.py
@@ -0,0 +1,116 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Removing unique constraint on 'StudentModule', fields ['module_id', 'module_type', 'student']
+ db.delete_unique('courseware_studentmodule', ['module_id', 'module_type', 'student_id'])
+
+ # Adding field 'StudentModule.max_grade'
+ db.add_column('courseware_studentmodule', 'max_grade', self.gf('django.db.models.fields.FloatField')(null=True, blank=True), keep_default=False)
+
+ # Adding field 'StudentModule.done'
+ db.add_column('courseware_studentmodule', 'done', self.gf('django.db.models.fields.CharField')(default='na', max_length=8, db_index=True), keep_default=False)
+
+ # Adding unique constraint on 'StudentModule', fields ['module_id', 'student']
+ db.create_unique('courseware_studentmodule', ['module_id', 'student_id'])
+
+
+ def backwards(self, orm):
+
+ # Removing unique constraint on 'StudentModule', fields ['module_id', 'student']
+ db.delete_unique('courseware_studentmodule', ['module_id', 'student_id'])
+
+ # Deleting field 'StudentModule.max_grade'
+ db.delete_column('courseware_studentmodule', 'max_grade')
+
+ # Deleting field 'StudentModule.done'
+ db.delete_column('courseware_studentmodule', 'done')
+
+ # Adding unique constraint on 'StudentModule', fields ['module_id', 'module_type', 'student']
+ db.create_unique('courseware_studentmodule', ['module_id', 'module_type', 'student_id'])
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
+ 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+ 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
+ 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'courseware.studentmodule': {
+ 'Meta': {'unique_together': "(('student', 'module_id'),)", 'object_name': 'StudentModule'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'done': ('django.db.models.fields.CharField', [], {'default': "'na'", 'max_length': '8', 'db_index': 'True'}),
+ 'grade': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'module_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'module_type': ('django.db.models.fields.CharField', [], {'default': "'problem'", 'max_length': '32', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ }
+ }
+
+ complete_apps = ['courseware']
diff --git a/courseware/models.py b/courseware/models.py
index f713da3195..a41e58eb81 100644
--- a/courseware/models.py
+++ b/courseware/models.py
@@ -31,8 +31,13 @@ class StudentModule(models.Model):
## Grade, and are we done?
grade = models.FloatField(null=True, blank=True, db_index=True)
- #max_grade = models.FloatField(null=True, blank=True)
-
+ max_grade = models.FloatField(null=True, blank=True)
+ DONE_TYPES = (('na','NOT_APPLICABLE'),
+ ('f','FINISHED'),
+ ('i','INCOMPLETE'),
+ )
+ done = models.CharField(max_length=8, choices=DONE_TYPES, default='na', db_index=True)
+
# DONE_TYPES = (('done','DONE'), # Finished
# ('incomplete','NOTDONE'), # Not finished
# ('na','NA')) # Not applicable (e.g. vertical)
diff --git a/courseware/module_render.py b/courseware/module_render.py
index 3c863684be..92a78528f9 100644
--- a/courseware/module_render.py
+++ b/courseware/module_render.py
@@ -26,24 +26,10 @@ import track.views
import courseware.content_parser as content_parser
-import courseware.modules.capa_module
-import courseware.modules.html_module
-import courseware.modules.schematic_module
-import courseware.modules.seq_module
-import courseware.modules.vertical_module
-import courseware.modules.video_module
+import courseware.modules
log = logging.getLogger("mitx.courseware")
-## TODO: Add registration mechanism
-modx_modules={'problem':courseware.modules.capa_module.LoncapaModule,
- 'video':courseware.modules.video_module.VideoModule,
- 'html':courseware.modules.html_module.HtmlModule,
- 'vertical':courseware.modules.vertical_module.VerticalModule,
- 'sequential':courseware.modules.seq_module.SequentialModule,
- 'tab':courseware.modules.seq_module.SequentialModule,
- 'schematic':courseware.modules.schematic_module.SchematicModule}
-
def object_cache(cache, user, module_type, module_id):
# We don't look up on user -- all queries include user
# Additional lookup would require a DB hit the way Django
@@ -74,18 +60,16 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
ajax_url = '/modx/'+module+'/'+id+'/'
- id_tag=modx_modules[module].id_attribute
-
# Grab the XML corresponding to the request from course.xml
- xml = content_parser.module_xml(content_parser.course_file(request.user), module, id_tag, id)
+ xml = content_parser.module_xml(content_parser.course_file(request.user), module, 'id', id)
# Create the module
- instance=modx_modules[module](xml,
- s.module_id,
- ajax_url=ajax_url,
- state=s.state,
- track_function = make_track_function(request),
- render_function = None)
+ instance=courseware.modules.get_module_class(module)(xml,
+ s.module_id,
+ ajax_url=ajax_url,
+ state=s.state,
+ track_function = make_track_function(request),
+ render_function = None)
# Let the module handle the AJAX
ajax_return=instance.handle_ajax(dispatch, request.POST)
# Save the state back to the database
@@ -100,7 +84,7 @@ def render_x_module(user, request, xml_module, module_object_preload):
''' Generic module for extensions. This renders to HTML. '''
# Check if problem has an instance in DB
module_type=xml_module.tag
- module_class=modx_modules[module_type]
+ module_class=courseware.modules.get_module_class(module_type)
module_id=xml_module.get('id') #module_class.id_attribute) or ""
# Grab state from database
@@ -132,7 +116,10 @@ def render_x_module(user, request, xml_module, module_object_preload):
smod.save() # This may be optional (at least in the case of no instance in the dB)
module_object_preload.append(smod)
# Grab content
- content = {'content':instance.get_html(),
+ content = instance.get_html()
+ if user.is_staff:
+ content=content+render_to_string("staff_problem_info.html", {'xml':etree.tostring(xml_module)})
+ content = {'content':content,
"destroy_js":instance.get_destroy_js(),
'init_js':instance.get_init_js(),
'type':module_type}
diff --git a/courseware/modules/__init__.py b/courseware/modules/__init__.py
index e69de29bb2..b20ce3778c 100644
--- a/courseware/modules/__init__.py
+++ b/courseware/modules/__init__.py
@@ -0,0 +1,69 @@
+import os
+import os.path
+
+from django.conf import settings
+
+import capa_module
+import html_module
+import schematic_module
+import seq_module
+import template_module
+import vertical_module
+import video_module
+
+from courseware import content_parser
+
+# Import all files in modules directory, excluding backups (# and . in name)
+# and __init__
+#
+# Stick them in a list
+# modx_module_list = []
+
+# for f in os.listdir(os.path.dirname(__file__)):
+# if f!='__init__.py' and \
+# f[-3:] == ".py" and \
+# "." not in f[:-3] \
+# and '#' not in f:
+# mod_path = 'courseware.modules.'+f[:-3]
+# mod = __import__(mod_path, fromlist = "courseware.modules")
+# if 'Module' in mod.__dict__:
+# modx_module_list.append(mod)
+
+#print modx_module_list
+modx_module_list = [capa_module, html_module, schematic_module, seq_module, template_module, vertical_module, video_module]
+#print modx_module_list
+
+modx_modules = {}
+
+# Convert list to a dictionary for lookup by tag
+def update_modules():
+ global modx_modules
+ modx_modules = dict()
+ for module in modx_module_list:
+ for tag in module.Module.get_xml_tags():
+ modx_modules[tag] = module.Module
+
+update_modules()
+
+def get_module_class(tag):
+ ''' Given an XML tag (e.g. 'video'), return
+ the associated module (e.g. video_module.Module).
+ '''
+ if tag not in modx_modules:
+ update_modules()
+ return modx_modules[tag]
+
+def get_module_id(tag):
+ ''' Given an XML tag (e.g. 'video'), return
+ the default ID for that module (e.g. 'youtube_id')
+ '''
+ return modx_modules[tag].id_attribute
+
+def get_valid_tags():
+ return modx_modules.keys()
+
+def get_default_ids():
+ tags = get_valid_tags()
+ ids = map(get_module_id, tags)
+ return dict(zip(tags, ids))
+
diff --git a/courseware/modules/capa_module.py b/courseware/modules/capa_module.py
index cd740d7dea..17ee6264bd 100644
--- a/courseware/modules/capa_module.py
+++ b/courseware/modules/capa_module.py
@@ -21,20 +21,23 @@ from mitxmako.shortcuts import render_to_response, render_to_string
from django.http import Http404
from x_module import XModule
-from courseware.capa.capa_problem import LoncapaProblem
+from courseware.capa.capa_problem import LoncapaProblem, StudentInputError
import courseware.content_parser as content_parser
log = logging.getLogger("mitx.courseware")
-class LoncapaModule(XModule):
+class Module(XModule):
''' Interface between capa_problem and x_module. Originally a hack
meant to be refactored out, but it seems to be serving a useful
prupose now. We can e.g .destroy and create the capa_problem on a
reset.
'''
- xml_tags = ["problem"]
+
id_attribute = "filename"
+ @classmethod
+ def get_xml_tags(c):
+ return ["problem"]
def get_state(self):
state = self.lcp.get_state()
@@ -78,7 +81,7 @@ class LoncapaModule(XModule):
# User submitted a problem, and hasn't reset. We don't want
# more submissions.
- if self.lcp.done and self.rerandomize:
+ if self.lcp.done and self.rerandomize == "always":
#print "!"
check_button = False
save_button = False
@@ -91,6 +94,10 @@ class LoncapaModule(XModule):
if self.max_attempts != None:
attempts_str = " ({a}/{m})".format(a=self.attempts, m=self.max_attempts)
+ # We don't need a "save" button if infinite number of attempts and non-randomized
+ if self.max_attempts == None and self.rerandomize != "always":
+ save_button = False
+
# Check if explanation is available, and if so, give a link
explain=""
if self.lcp.done and self.explain_available=='attempted':
@@ -157,12 +164,12 @@ class LoncapaModule(XModule):
self.show_answer="closed"
self.rerandomize=content_parser.item(dom2.xpath('/problem/@rerandomize'))
- if self.rerandomize=="":
- self.rerandomize=True
- elif self.rerandomize=="false":
- self.rerandomize=False
- elif self.rerandomize=="true":
- self.rerandomize=True
+ if self.rerandomize=="" or self.rerandomize=="always" or self.rerandomize=="true":
+ self.rerandomize="always"
+ elif self.rerandomize=="false" or self.rerandomize=="per_student":
+ self.rerandomize="per_student"
+ elif self.rerandomize=="never":
+ self.rerandomize="never"
else:
raise Exception("Invalid rerandomize attribute "+self.rerandomize)
@@ -172,9 +179,13 @@ class LoncapaModule(XModule):
self.attempts=state['attempts']
self.filename=content_parser.item(dom2.xpath('/problem/@filename'))
- filename=settings.DATA_DIR+"problems/"+self.filename+".xml"
+ filename=settings.DATA_DIR+"/problems/"+self.filename+".xml"
self.name=content_parser.item(dom2.xpath('/problem/@name'))
- self.lcp=LoncapaProblem(filename, self.item_id, state)
+ if self.rerandomize == 'never':
+ seed = 1
+ else:
+ seed = None
+ self.lcp=LoncapaProblem(filename, self.item_id, state, seed = seed)
def handle_ajax(self, dispatch, get):
if dispatch=='problem_get':
@@ -250,7 +261,7 @@ class LoncapaModule(XModule):
for key in get:
answers['_'.join(key.split('_')[1:])]=get[key]
- print "XXX", answers, get
+# print "XXX", answers, get
event_info['answers']=answers
@@ -263,7 +274,7 @@ class LoncapaModule(XModule):
# Problem submitted. Student should reset before checking
# again.
- if self.lcp.done and self.rerandomize:
+ if self.lcp.done and self.rerandomize == "always":
event_info['failure']='unreset'
self.tracker('save_problem_check_fail', event_info)
print "cpdr"
@@ -274,22 +285,27 @@ class LoncapaModule(XModule):
lcp_id = self.lcp.problem_id
filename = self.lcp.filename
correct_map = self.lcp.grade_answers(answers)
+ except StudentInputError as inst:
+ self.lcp = LoncapaProblem(filename, id=lcp_id, state=old_state)
+ traceback.print_exc()
+# print {'error':sys.exc_info(),
+# 'answers':answers,
+# 'seed':self.lcp.seed,
+# 'filename':self.lcp.filename}
+ return json.dumps({'success':inst.message})
except:
self.lcp = LoncapaProblem(filename, id=lcp_id, state=old_state)
traceback.print_exc()
- print {'error':sys.exc_info(),
- 'answers':answers,
- 'seed':self.lcp.seed,
- 'filename':self.lcp.filename}
- return json.dumps({'success':'syntax'})
+ return json.dumps({'success':'Unknown Error'})
+
self.attempts = self.attempts + 1
self.lcp.done=True
- success = 'finished'
+ success = 'correct'
for i in correct_map:
if correct_map[i]!='correct':
- success = 'errors'
+ success = 'incorrect'
js=json.dumps({'correct_map' : correct_map,
'success' : success})
@@ -319,7 +335,7 @@ class LoncapaModule(XModule):
# Problem submitted. Student should reset before saving
# again.
- if self.lcp.done and self.rerandomize:
+ if self.lcp.done and self.rerandomize == "always":
event_info['failure']='done'
self.tracker('save_problem_fail', event_info)
return "Problem needs to be reset prior to save."
@@ -352,7 +368,7 @@ class LoncapaModule(XModule):
self.lcp.student_answers = dict()
- if self.rerandomize:
+ if self.rerandomize == "always":
self.lcp.context=dict()
self.lcp.questions=dict() # Detailed info about questions in problem instance. TODO: Should be by id and not lid.
self.lcp.seed=None
diff --git a/courseware/modules/html_module.py b/courseware/modules/html_module.py
index f1ce14ec7e..4194f73e74 100644
--- a/courseware/modules/html_module.py
+++ b/courseware/modules/html_module.py
@@ -7,14 +7,15 @@ from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
from lxml import etree
-class HtmlModule(XModule):
+class Module(XModule):
id_attribute = 'filename'
def get_state(self):
return json.dumps({ })
- def get_xml_tags():
- return "html"
+ @classmethod
+ def get_xml_tags(c):
+ return ["html"]
def get_html(self):
if self.filename==None:
@@ -23,7 +24,7 @@ class HtmlModule(XModule):
textlist=[i for i in textlist if type(i)==str]
return "".join(textlist)
try:
- filename=settings.DATA_DIR+"html/"+self.filename+".xml"
+ filename=settings.DATA_DIR+"html/"+self.filename
return open(filename).read()
except: # For backwards compatibility. TODO: Remove
return render_to_string(self.filename, {'id': self.item_id})
diff --git a/courseware/modules/schematic_module.py b/courseware/modules/schematic_module.py
index 0312321b4d..e253f1acc6 100644
--- a/courseware/modules/schematic_module.py
+++ b/courseware/modules/schematic_module.py
@@ -6,14 +6,15 @@ from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
-class SchematicModule(XModule):
+class Module(XModule):
id_attribute = 'id'
def get_state(self):
return json.dumps({ })
- def get_xml_tags():
- return "schematic"
+ @classmethod
+ def get_xml_tags(c):
+ return ["schematic"]
def get_html(self):
return ''.format(item_id=self.item_id)
diff --git a/courseware/modules/seq_module.py b/courseware/modules/seq_module.py
index 33c4088486..02796a9768 100644
--- a/courseware/modules/seq_module.py
+++ b/courseware/modules/seq_module.py
@@ -13,7 +13,7 @@ from x_module import XModule
# OBSOLETE: This obsoletes 'type'
class_priority = ['video', 'problem']
-class SequentialModule(XModule):
+class Module(XModule):
''' Layout module which lays out content in a temporal sequence
'''
id_attribute = 'id'
@@ -21,7 +21,8 @@ class SequentialModule(XModule):
def get_state(self):
return json.dumps({ 'position':self.position })
- def get_xml_tags():
+ @classmethod
+ def get_xml_tags(c):
return ["sequential", 'tab']
def get_html(self):
@@ -65,28 +66,34 @@ class SequentialModule(XModule):
## Returns a set of all types of all sub-children
child_classes = [set([i.tag for i in e.iter()]) for e in self.xmltree]
- self.contents=[(e.get("name"),j(self.render_function(e))) \
- for e in self.xmltree]
+ self.titles = json.dumps(["\n".join([i.get("name").strip() for i in e.iter() if i.get("name") != None]) \
+ for e in self.xmltree])
+
+ self.contents = [j(self.render_function(e)) \
+ for e in self.xmltree]
+
+ print self.titles
for (content, element_class) in zip(self.contents, child_classes):
new_class = 'other'
for c in class_priority:
if c in element_class:
new_class = c
- content[1]['type'] = new_class
+ content['type'] = new_class
js=""
params={'items':self.contents,
'id':self.item_id,
- 'position': self.position}
+ 'position': self.position,
+ 'titles':self.titles}
# TODO/BUG: Destroy JavaScript should only be called for the active view
# This calls it for all the views
#
# To fix this, we'd probably want to have some way of assigning unique
# IDs to sequences.
- destroy_js="".join([e[1]['destroy_js'] for e in self.contents if 'destroy_js' in e[1]])
+ destroy_js="".join([e['destroy_js'] for e in self.contents if 'destroy_js' in e])
if self.xmltree.tag == 'sequential':
self.init_js=js+render_to_string('seq_module.js',params)
diff --git a/courseware/modules/template_module.py b/courseware/modules/template_module.py
new file mode 100644
index 0000000000..d9dbb613f0
--- /dev/null
+++ b/courseware/modules/template_module.py
@@ -0,0 +1,29 @@
+import json
+import os
+
+## TODO: Abstract out from Django
+from django.conf import settings
+from mitxmako.shortcuts import render_to_response, render_to_string
+
+from x_module import XModule
+from lxml import etree
+
+class Module(XModule):
+ def get_state(self):
+ return json.dumps({ })
+
+ @classmethod
+ def get_xml_tags(c):
+ tags = os.listdir(settings.DATA_DIR+'/custom_tags')
+ return tags
+
+ def get_html(self):
+ return self.html
+
+ def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None):
+ XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
+ xmltree = etree.fromstring(xml)
+ filename = xmltree.tag
+ params = dict(xmltree.items())
+# print params
+ self.html = render_to_string(filename, params, namespace = 'custom_tags')
diff --git a/courseware/modules/vertical_module.py b/courseware/modules/vertical_module.py
index 6939c75cc9..c068cb9a76 100644
--- a/courseware/modules/vertical_module.py
+++ b/courseware/modules/vertical_module.py
@@ -7,14 +7,15 @@ from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
from lxml import etree
-class VerticalModule(XModule):
+class Module(XModule):
id_attribute = 'id'
def get_state(self):
return json.dumps({ })
- def get_xml_tags():
- return "vertical"
+ @classmethod
+ def get_xml_tags(c):
+ return ["vertical"]
def get_html(self):
return render_to_string('vert_module.html',{'items':self.contents})
diff --git a/courseware/modules/video_module.py b/courseware/modules/video_module.py
index b7e1e211dc..2063c18953 100644
--- a/courseware/modules/video_module.py
+++ b/courseware/modules/video_module.py
@@ -11,8 +11,8 @@ from x_module import XModule
log = logging.getLogger("mitx.courseware.modules")
-class VideoModule(XModule):
- #id_attribute = 'youtube'
+class Module(XModule):
+ id_attribute = 'youtube'
video_time = 0
def handle_ajax(self, dispatch, get):
@@ -28,9 +28,10 @@ class VideoModule(XModule):
log.debug(u"STATE POSITION {0}".format(self.position))
return json.dumps({ 'position':self.position })
- def get_xml_tags():
+ @classmethod
+ def get_xml_tags(c):
'''Tags in the courseware file guaranteed to correspond to the module'''
- return "video"
+ return ["video"]
def video_list(self):
l = self.youtube.split(',')
diff --git a/courseware/modules/x_module.py b/courseware/modules/x_module.py
index 4b45c4c7fc..4a379253c4 100644
--- a/courseware/modules/x_module.py
+++ b/courseware/modules/x_module.py
@@ -8,9 +8,10 @@ class XModule(object):
Initialized on access with __init__, first time with state=None, and
then with state
'''
- id_attribute='name' # An attribute guaranteed to be unique
+ id_attribute='id' # An attribute guaranteed to be unique
- def get_xml_tags():
+ @classmethod
+ def get_xml_tags(c):
''' Tags in the courseware file guaranteed to correspond to the module '''
return []
diff --git a/courseware/tests.py b/courseware/tests.py
index 501deb776c..b0d7cad19f 100644
--- a/courseware/tests.py
+++ b/courseware/tests.py
@@ -1,16 +1,53 @@
-"""
-This file demonstrates writing tests using the unittest module. These will pass
-when you run "manage.py test".
+import unittest
-Replace this with more appropriate tests for your application.
-"""
+import numpy
-from django.test import TestCase
+import courseware.modules
+import courseware.capa.calc as calc
+class ModelsTest(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def test_get_module_class(self):
+ vc = courseware.modules.get_module_class('video')
+ vc_str = ""
+ self.assertEqual(str(vc), vc_str)
+ video_id = courseware.modules.get_default_ids()['video']
+ self.assertEqual(video_id, 'youtube')
+
+ def test_calc(self):
+ variables={'R1':2.0, 'R3':4.0}
+ functions={'sin':numpy.sin, 'cos':numpy.cos}
+
+ self.assertEqual(calc.evaluator(variables, functions, "10000||sin(7+5)-6k"), 4000.0)
+ self.assertEqual(calc.evaluator({'R1': 2.0, 'R3':4.0}, {}, "13"), 13)
+ self.assertEqual(calc.evaluator(variables, functions, "13"), 13)
+ self.assertEqual(calc.evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5"), 5)
+ self.assertEqual(calc.evaluator({},{}, "-1"), -1)
+ self.assertEqual(calc.evaluator({},{}, "-0.33"), -.33)
+ self.assertEqual(calc.evaluator({},{}, "-.33"), -.33)
+ self.assertEqual(calc.evaluator(variables, functions, "R1*R3"), 8.0)
+ self.assertTrue(abs(calc.evaluator(variables, functions, "sin(e)-0.41"))<0.01)
+ self.assertTrue(abs(calc.evaluator(variables, functions, "k*T/q-0.025"))<0.001)
+ exception_happened = False
+ try:
+ calc.evaluator({},{}, "5+7 QWSEKO")
+ except:
+ exception_happened = True
+ self.assertTrue(exception_happened)
+
+ try:
+ calc.evaluator({'r1':5},{}, "r1+r2")
+ except calc.UndefinedVariable:
+ pass
+
+ self.assertEqual(calc.evaluator(variables, functions, "r1*r3"), 8.0)
+
+ exception_happened = False
+ try:
+ calc.evaluator(variables, functions, "r1*r3", cs=True)
+ except:
+ exception_happened = True
+ self.assertTrue(exception_happened)
-class SimpleTest(TestCase):
- def test_basic_addition(self):
- """
- Tests that 1 + 1 always equals 2.
- """
- self.assertEqual(1 + 1, 2)
diff --git a/courseware/views.py b/courseware/views.py
index 5a63fb3039..b6f2644123 100644
--- a/courseware/views.py
+++ b/courseware/views.py
@@ -34,6 +34,38 @@ etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False,
template_imports={'urllib':urllib}
+def get_grade(request, problem, cache):
+ ## HACK: assumes max score is fixed per problem
+ id = problem.get('id')
+ correct = 0
+
+ # If the ID is not in the cache, add the item
+ if id not in cache:
+ module = StudentModule(module_type = 'problem', # TODO: Move into StudentModule.__init__?
+ module_id = id,
+ student = request.user,
+ state = None,
+ grade = 0,
+ max_grade = None,
+ done = 'i')
+ cache[id] = module
+
+ # Grab the # correct from cache
+ if id in cache:
+ response = cache[id]
+ if response.grade!=None:
+ correct=response.grade
+
+ # Grab max grade from cache, or if it doesn't exist, compute and save to DB
+ if id in cache and response.max_grade != None:
+ total = response.max_grade
+ else:
+ total=courseware.modules.capa_module.Module(etree.tostring(problem), "id").max_score()
+ response.max_grade = total
+ response.save()
+
+ return (correct, total)
+
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def profile(request):
''' User profile. Show username, location, etc, as well as grades .
@@ -42,46 +74,48 @@ def profile(request):
return redirect('/')
dom=content_parser.course_file(request.user)
- hw=[]
course = dom.xpath('//course/@name')[0]
- chapters = dom.xpath('//course[@name=$course]/chapter', course=course)
+ xmlChapters = dom.xpath('//course[@name=$course]/chapter', course=course)
responses=StudentModule.objects.filter(student=request.user)
response_by_id = {}
for response in responses:
response_by_id[response.module_id] = response
-
-
+
+
total_scores = {}
-
- for c in chapters:
+ chapters=[]
+ for c in xmlChapters:
+ sections = []
chname=c.get('name')
for s in dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section',
course=course, chname=chname):
problems=dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section[@name=$section]//problem',
course=course, chname=chname, section=s.get('name'))
-
+
graded = True if s.get('graded') == "true" else False
scores=[]
if len(problems)>0:
for p in problems:
- id = p.get('id')
- correct = 0
- if id in response_by_id:
- response = response_by_id[id]
- if response.grade!=None:
- correct=response.grade
-
- total=courseware.modules.capa_module.LoncapaModule(etree.tostring(p), "id").max_score() # TODO: Add state. Not useful now, but maybe someday problems will have randomized max scores?
+ (correct,total) = get_grade(request, p, response_by_id)
+ # id = p.get('id')
+ # correct = 0
+ # if id in response_by_id:
+ # response = response_by_id[id]
+ # if response.grade!=None:
+ # correct=response.grade
+
+ # total=courseware.modules.capa_module.Module(etree.tostring(p), "id").max_score() # TODO: Add state. Not useful now, but maybe someday problems will have randomized max scores?
+ # print correct, total
scores.append((int(correct),total, graded ))
-
-
+
+
section_total = (sum([score[0] for score in scores]),
sum([score[1] for score in scores]))
-
+
graded_total = (sum([score[0] for score in scores if score[2]]),
sum([score[1] for score in scores if score[2]]))
-
+
#Add the graded total to total_scores
format = s.get('format') if s.get('format') else ""
subtitle = s.get('subtitle') if s.get('subtitle') else format
@@ -89,10 +123,8 @@ def profile(request):
format_scores = total_scores[ format ] if format in total_scores else []
format_scores.append( graded_total )
total_scores[ format ] = format_scores
-
- score={'course':course,
- 'section':s.get("name"),
- 'chapter':c.get("name"),
+
+ score={'section':s.get("name"),
'scores':scores,
'section_total' : section_total,
'format' : format,
@@ -100,7 +132,12 @@ def profile(request):
'due' : s.get("due") or "",
'graded' : graded,
}
- hw.append(score)
+ sections.append(score)
+
+ chapters.append({'course':course,
+ 'chapter' : c.get("name"),
+ 'sections' : sections,})
+
def totalWithDrops(scores, drop_count):
#Note that this key will sort the list descending
@@ -216,11 +253,12 @@ def profile(request):
'location':user_info.location,
'language':user_info.language,
'email':request.user.email,
- 'homeworks':hw,
+ 'chapters':chapters,
'format_url_params' : format_url_params,
'grade_summary' : grade_summary,
'csrf':csrf(request)['csrf_token']
}
+
return render_to_response('profile.html', context)
def format_url_params(params):
@@ -244,6 +282,40 @@ def render_accordion(request,course,chapter,section):
return {'init_js':render_to_string('accordion_init.js',context),
'content':render_to_string('accordion.html',context)}
+@cache_control(no_cache=True, no_store=True, must_revalidate=True)
+def render_section(request, section):
+ ''' TODO: Consolidate with index
+ '''
+ user = request.user
+ if not settings.COURSEWARE_ENABLED or not user.is_authenticated():
+ return redirect('/')
+
+# try:
+ dom = content_parser.section_file(user, section)
+ #except:
+ # raise Http404
+
+ accordion=render_accordion(request, '', '', '')
+
+ module_ids = dom.xpath("//@id")
+
+ module_object_preload = list(StudentModule.objects.filter(student=user,
+ module_id__in=module_ids))
+
+ module=render_module(user, request, dom, module_object_preload)
+
+ if 'init_js' not in module:
+ module['init_js']=''
+
+ context={'init':accordion['init_js']+module['init_js'],
+ 'accordion':accordion['content'],
+ 'content':module['content'],
+ 'csrf':csrf(request)['csrf_token']}
+
+ result = render_to_response('courseware.html', context)
+ return result
+
+
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def index(request, course="6.002 Spring 2012", chapter="Using the System", section="Hints"):
''' Displays courseware accordion, and any associated content.
diff --git a/mitxmako/middleware.py b/mitxmako/middleware.py
index 481d1db672..857ae29541 100644
--- a/mitxmako/middleware.py
+++ b/mitxmako/middleware.py
@@ -17,33 +17,31 @@ import tempfile
from django.template import RequestContext
requestcontext = None
-lookup = None
+lookup = {}
class MakoMiddleware(object):
def __init__(self):
"""Setup mako variables and lookup object"""
from django.conf import settings
# Set all mako variables based on django settings
- global template_dirs, output_encoding, module_directory, encoding_errors
- directories = getattr(settings, 'MAKO_TEMPLATE_DIRS', settings.TEMPLATE_DIRS)
-
+ template_locations = settings.MAKO_TEMPLATES
module_directory = getattr(settings, 'MAKO_MODULE_DIR', None)
+
if module_directory is None:
module_directory = tempfile.mkdtemp()
- output_encoding = getattr(settings, 'MAKO_OUTPUT_ENCODING', 'utf-8')
- encoding_errors = getattr(settings, 'MAKO_ENCODING_ERRORS', 'replace')
-
- global lookup
- lookup = TemplateLookup(directories=directories,
+ for location in template_locations:
+ lookup[location] = TemplateLookup(directories=template_locations[location],
module_directory=module_directory,
- output_encoding=output_encoding,
- encoding_errors=encoding_errors,
+ output_encoding='utf-8',
+ input_encoding='utf-8',
+ encoding_errors='replace',
)
+
import mitxmako
mitxmako.lookup = lookup
def process_request (self, request):
global requestcontext
requestcontext = RequestContext(request)
-# print requestcontext
+
diff --git a/mitxmako/shortcuts.py b/mitxmako/shortcuts.py
index 261cee5b41..862f8b96bb 100644
--- a/mitxmako/shortcuts.py
+++ b/mitxmako/shortcuts.py
@@ -20,7 +20,7 @@ from django.conf import settings
from mitxmako.middleware import requestcontext
-def render_to_string(template_name, dictionary, context_instance=None):
+def render_to_string(template_name, dictionary, context_instance=None, namespace='main'):
context_instance = context_instance or Context(dictionary)
# add dictionary to context_instance
context_instance.update(dictionary or {})
@@ -31,12 +31,12 @@ def render_to_string(template_name, dictionary, context_instance=None):
for d in context_instance:
context_dictionary.update(d)
# fetch and render template
- template = middleware.lookup.get_template(template_name)
+ template = middleware.lookup[namespace].get_template(template_name)
return template.render(**context_dictionary)
-def render_to_response(template_name, dictionary, context_instance=None, **kwargs):
+def render_to_response(template_name, dictionary, context_instance=None, namespace='main', **kwargs):
"""
Returns a HttpResponse whose content is filled with the result of calling
lookup.get_template(args[0]).render with the passed arguments.
"""
- return HttpResponse(render_to_string(template_name, dictionary, context_instance), **kwargs)
+ return HttpResponse(render_to_string(template_name, dictionary, context_instance, namespace), **kwargs)
diff --git a/settings.py b/settings.py
deleted file mode 120000
index f42725e7f1..0000000000
--- a/settings.py
+++ /dev/null
@@ -1 +0,0 @@
-settings_new_askbot.py
\ No newline at end of file
diff --git a/settings.py b/settings.py
new file mode 100644
index 0000000000..b35c9163ca
--- /dev/null
+++ b/settings.py
@@ -0,0 +1,645 @@
+import os
+import platform
+import sys
+import tempfile
+
+import djcelery
+
+# from settings2.askbotsettings import LIVESETTINGS_OPTIONS
+
+# Configuration option for when we want to grab server error pages
+STATIC_GRAB = False
+DEV_CONTENT = True
+
+LIB_URL = '/static/lib/'
+LIB_URL = 'https://mitxstatic.s3.amazonaws.com/js/'
+BOOK_URL = '/static/book/'
+BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/'
+
+# Feature Flags. These should be set to false until they are ready to deploy, and then eventually flag mechanisms removed
+GENERATE_PROFILE_SCORES = False # If this is true, random scores will be generated for the purpose of debugging the profile graphs
+
+# Our parent dir (mitx_all) is the BASE_DIR
+BASE_DIR = os.path.abspath(os.path.join(__file__, "..", ".."))
+
+COURSEWARE_ENABLED = True
+ASKBOT_ENABLED = True
+CSRF_COOKIE_DOMAIN = '127.0.0.1'
+
+# Defaults to be overridden
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+SITE_NAME = "localhost:8000"
+
+DEFAULT_FROM_EMAIL = 'registration@mitx.mit.edu'
+DEFAULT_FEEDBACK_EMAIL = 'feedback@mitx.mit.edu'
+
+GENERATE_RANDOM_USER_CREDENTIALS = False
+
+WIKI_REQUIRE_LOGIN_EDIT = True
+WIKI_REQUIRE_LOGIN_VIEW = True
+
+PERFSTATS = False
+
+HTTPS = 'on'
+
+MEDIA_URL = ''
+MEDIA_ROOT = ''
+
+# S3BotoStorage insists on a timeout for uploaded assets. We should make it
+# permanent instead, but rather than trying to figure out exactly where that
+# setting is, I'm just bumping the expiration time to something absurd (100
+# years). This is only used if DEFAULT_FILE_STORAGE is overriden to use S3
+# in the global settings.py
+AWS_QUERYSTRING_EXPIRE = 10 * 365 * 24 * 60 * 60 # 10 years
+
+# Needed for Askbot
+# Deployed machines: Move to S3
+DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ ('MITx Admins', 'admin@mitx.mit.edu'),
+)
+
+MANAGERS = ADMINS
+
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+TIME_ZONE = 'America/New_York'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+STATIC_URL = '/static/'
+
+# URL prefix for admin static files -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# 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',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'track.middleware.TrackMiddleware',
+ 'mitxmako.middleware.MakoMiddleware',
+ #'debug_toolbar.middleware.DebugToolbarMiddleware',
+)
+
+ROOT_URLCONF = 'mitx.urls'
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'courseware',
+ 'student',
+ 'django.contrib.humanize',
+ 'static_template_view',
+ 'staticbook',
+ 'simplewiki',
+ 'track',
+ 'circuit',
+ 'perfstats',
+ 'util',
+ # Uncomment the next line to enable the admin:
+ # 'django.contrib.admin',
+ # Uncomment the next line to enable admin documentation:
+ # 'django.contrib.admindocs',
+)
+
+#TRACK_DIR = None
+DEBUG_TRACK_LOG = False
+# Maximum length of a tracking string. We don't want e.g. a file upload in our log
+TRACK_MAX_EVENT = 10000
+# Maximum length of log file before starting a new one.
+MAXLOG = 500
+
+LOG_DIR = "/tmp/"
+MAKO_MODULE_DIR = None
+
+MAKO_TEMPLATES = {}
+
+# Make sure we execute correctly regardless of where we're called from
+execfile(os.path.join(BASE_DIR, "settings.py"))
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+
+pid = os.getpid()
+hostname = platform.node().split(".")[0]
+SYSLOG_ADDRESS = ('syslog.m.i4x.org', 514)
+
+handlers = ['console']
+if not DEBUG:
+ handlers.append('syslogger')
+
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': True,
+ 'formatters' : {
+ 'standard' : {
+ 'format' : '%(asctime)s %(levelname)s %(process)d [%(name)s] %(filename)s:%(lineno)d - %(message)s',
+ },
+ 'syslog_format' : {
+ 'format' : '[%(name)s] %(levelname)s [' + hostname + ' %(process)d] [%(filename)s:%(lineno)d] - %(message)s',
+ },
+ 'raw' : {
+ 'format' : '%(message)s',
+ }
+ },
+ 'handlers' : {
+ 'console' : {
+ 'level' : 'DEBUG' if DEBUG else 'INFO',
+ 'class' : 'logging.StreamHandler',
+ 'formatter' : 'standard',
+ 'stream' : sys.stdout,
+ },
+ 'console_err' : {
+ 'level' : 'ERROR',
+ 'class' : 'logging.StreamHandler',
+ 'formatter' : 'standard',
+ 'stream' : sys.stderr,
+ },
+ 'syslogger' : {
+ 'level' : 'INFO',
+ 'class' : 'logging.handlers.SysLogHandler',
+ 'address' : SYSLOG_ADDRESS,
+ 'formatter' : 'syslog_format',
+ },
+ 'mail_admins' : {
+ 'level': 'ERROR',
+ 'class': 'django.utils.log.AdminEmailHandler',
+ },
+ },
+ 'loggers' : {
+ 'django' : {
+ 'handlers' : handlers + ['mail_admins'],
+ 'propagate' : True,
+ 'level' : 'INFO'
+ },
+ 'tracking' : {
+ 'handlers' : [] if DEBUG else ['syslogger'], # handlers,
+ 'level' : 'DEBUG',
+ 'propagate' : False,
+ },
+ 'root' : {
+ 'handlers' : handlers,
+ 'level' : 'DEBUG',
+ 'propagate' : False
+ },
+ 'mitx' : {
+ 'handlers' : handlers,
+ 'level' : 'DEBUG',
+ 'propagate' : False
+ },
+ }
+}
+
+
+
+if PERFSTATS :
+ MIDDLEWARE_CLASSES = ( 'perfstats.middleware.ProfileMiddleware',) + MIDDLEWARE_CLASSES
+
+if 'TRACK_DIR' not in locals():
+ TRACK_DIR = BASE_DIR+'/track_dir/'
+if 'STATIC_ROOT' not in locals():
+ STATIC_ROOT = BASE_DIR+'/staticroot/'
+if 'DATA_DIR' not in locals():
+ DATA_DIR = BASE_DIR+'/data/'
+if 'TEXTBOOK_DIR' not in locals():
+ TEXTBOOK_DIR = BASE_DIR+'/textbook/'
+
+if 'TEMPLATE_DIRS' not in locals():
+ TEMPLATE_DIRS = (
+ BASE_DIR+'/templates/',
+ DATA_DIR+'/templates',
+ TEXTBOOK_DIR,
+ )
+
+if 'STATICFILES_DIRS' not in locals():
+ STATICFILES_DIRS = (
+ BASE_DIR+'/3rdParty/static',
+ BASE_DIR+'/static',
+ )
+
+
+if 'ASKBOT_EXTRA_SKINS_DIR' not in locals():
+ ASKBOT_EXTRA_SKINS_DIR = BASE_DIR+'/askbot-devel/askbot/skins'
+if 'ASKBOT_DIR' not in locals():
+ ASKBOT_DIR = BASE_DIR+'/askbot-devel'
+
+sys.path.append(ASKBOT_DIR)
+import askbot
+import site
+
+STATICFILES_DIRS = STATICFILES_DIRS + ( ASKBOT_DIR+'/askbot/skins',)
+
+ASKBOT_ROOT = os.path.dirname(askbot.__file__)
+
+# Needed for Askbot
+# Deployed machines: Move to S3
+if MEDIA_ROOT == '':
+ MEDIA_ROOT = ASKBOT_DIR+'/askbot/upfiles'
+if MEDIA_URL == '':
+ MEDIA_URL = '/discussion/upfiles/'
+
+site.addsitedir(os.path.join(os.path.dirname(askbot.__file__), 'deps'))
+TEMPLATE_LOADERS = TEMPLATE_LOADERS + ('askbot.skins.loaders.filesystem_load_template_source',)
+
+MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
+ 'util.middleware.ExceptionLoggingMiddleware',
+ 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware',
+ 'askbot.middleware.forum_mode.ForumModeMiddleware',
+ 'askbot.middleware.cancel.CancelActionMiddleware',
+ 'django.middleware.transaction.TransactionMiddleware',
+ #'debug_toolbar.middleware.DebugToolbarMiddleware',
+ 'askbot.middleware.view_log.ViewLogMiddleware',
+ 'askbot.middleware.spaceless.SpacelessMiddleware',
+ # 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
+)
+
+FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
+FILE_UPLOAD_HANDLERS = (
+ 'django.core.files.uploadhandler.MemoryFileUploadHandler',
+ 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
+)
+ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
+ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 #result in bytes
+# ASKBOT_FILE_UPLOAD_DIR = os.path.join(os.path.dirname(__file__), 'askbot', 'upfiles')
+
+PROJECT_ROOT = os.path.dirname(__file__)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ 'django.core.context_processors.request',
+ 'askbot.context.application_settings',
+ #'django.core.context_processors.i18n',
+ 'askbot.user_messages.context_processors.user_messages',#must be before auth
+ 'django.core.context_processors.auth', #this is required for admin
+ 'django.core.context_processors.csrf', #necessary for csrf protection
+)
+
+INSTALLED_APPS = INSTALLED_APPS + (
+ 'django.contrib.sitemaps',
+ 'django.contrib.admin',
+ 'south',
+ 'askbot.deps.livesettings',
+ 'askbot',
+ 'keyedcache', # TODO: Main askbot tree has this installed, but we get intermittent errors if we include it.
+ 'robots',
+ 'django_countries',
+ 'djcelery',
+ 'djkombu',
+ 'followit',
+)
+
+CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
+ASKBOT_URL = 'discussion/'
+LOGIN_REDIRECT_URL = '/'
+LOGIN_URL = '/'
+
+# ASKBOT_UPLOADED_FILES_URL = '%s%s' % (ASKBOT_URL, 'upfiles/')
+ALLOW_UNICODE_SLUGS = False
+ASKBOT_USE_STACKEXCHANGE_URLS = False #mimic url scheme of stackexchange
+ASKBOT_CSS_DEVEL = True
+
+LIVESETTINGS_OPTIONS = {
+ 1: {
+ 'DB' : False,
+ 'SETTINGS' : {
+ 'ACCESS_CONTROL' : {
+ 'ASKBOT_CLOSED_FORUM_MODE' : True,
+ },
+ 'BADGES' : {
+ 'DISCIPLINED_BADGE_MIN_UPVOTES' : 3,
+ 'PEER_PRESSURE_BADGE_MIN_DOWNVOTES' : 3,
+ 'TEACHER_BADGE_MIN_UPVOTES' : 1,
+ 'NICE_ANSWER_BADGE_MIN_UPVOTES' : 2,
+ 'GOOD_ANSWER_BADGE_MIN_UPVOTES' : 3,
+ 'GREAT_ANSWER_BADGE_MIN_UPVOTES' : 5,
+ 'NICE_QUESTION_BADGE_MIN_UPVOTES' : 2,
+ 'GOOD_QUESTION_BADGE_MIN_UPVOTES' : 3,
+ 'GREAT_QUESTION_BADGE_MIN_UPVOTES' : 5,
+ 'POPULAR_QUESTION_BADGE_MIN_VIEWS' : 150,
+ 'NOTABLE_QUESTION_BADGE_MIN_VIEWS' : 250,
+ 'FAMOUS_QUESTION_BADGE_MIN_VIEWS' : 500,
+ 'SELF_LEARNER_BADGE_MIN_UPVOTES' : 1,
+ 'CIVIC_DUTY_BADGE_MIN_VOTES' : 100,
+ 'ENLIGHTENED_BADGE_MIN_UPVOTES' : 3,
+ 'ASSOCIATE_EDITOR_BADGE_MIN_EDITS' : 20,
+ 'COMMENTATOR_BADGE_MIN_COMMENTS' : 10,
+ 'ENTHUSIAST_BADGE_MIN_DAYS' : 30,
+ 'FAVORITE_QUESTION_BADGE_MIN_STARS' : 3,
+ 'GURU_BADGE_MIN_UPVOTES' : 5,
+ 'NECROMANCER_BADGE_MIN_DELAY' : 30,
+ 'NECROMANCER_BADGE_MIN_UPVOTES' : 1,
+ 'STELLAR_QUESTION_BADGE_MIN_STARS' : 5,
+ 'TAXONOMIST_BADGE_MIN_USE_COUNT' : 10,
+ },
+ 'EMAIL' : {
+ 'EMAIL_SUBJECT_PREFIX' : u'[Django] ',
+ 'EMAIL_UNIQUE' : True,
+ 'EMAIL_VALIDATION' : False,
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_M_AND_C' : u'w',
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ALL' : u'w',
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ANS' : u'w',
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ASK' : u'w',
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_SEL' : u'w',
+ 'ENABLE_UNANSWERED_REMINDERS' : False,
+ 'DAYS_BEFORE_SENDING_UNANSWERED_REMINDER' : 1,
+ 'UNANSWERED_REMINDER_FREQUENCY' : 1,
+ 'MAX_UNANSWERED_REMINDERS' : 5,
+ 'ENABLE_ACCEPT_ANSWER_REMINDERS' : False,
+ 'DAYS_BEFORE_SENDING_ACCEPT_ANSWER_REMINDER' : 3,
+ 'ACCEPT_ANSWER_REMINDER_FREQUENCY' : 3,
+ 'MAX_ACCEPT_ANSWER_REMINDERS' : 5,
+ 'ANONYMOUS_USER_EMAIL' : u'anonymous@askbot.org',
+ 'ALLOW_ASKING_BY_EMAIL' : False,
+ 'REPLACE_SPACE_WITH_DASH_IN_EMAILED_TAGS' : True,
+ 'MAX_ALERTS_PER_EMAIL' : 7,
+ },
+ 'EMBEDDABLE_WIDGETS' : {
+ 'QUESTIONS_WIDGET_CSS' : u"\nbody {\n overflow: hidden;\n}\n#container {\n width: 200px;\n height: 350px;\n}\nul {\n list-style: none;\n padding: 5px;\n margin: 5px;\n}\nli {\n border-bottom: #CCC 1px solid;\n padding-bottom: 5px;\n padding-top: 5px;\n}\nli:last-child {\n border: none;\n}\na {\n text-decoration: none;\n color: #464646;\n font-family: 'Yanone Kaffeesatz', sans-serif;\n font-size: 15px;\n}\n",
+ 'QUESTIONS_WIDGET_FOOTER' : u"\n\n",
+ 'QUESTIONS_WIDGET_HEADER' : u'',
+ 'QUESTIONS_WIDGET_MAX_QUESTIONS' : 7,
+ },
+ 'EXTERNAL_KEYS' : {
+ 'RECAPTCHA_KEY' : u'',
+ 'RECAPTCHA_SECRET' : u'',
+ 'FACEBOOK_KEY' : u'',
+ 'FACEBOOK_SECRET' : u'',
+ 'HOW_TO_CHANGE_LDAP_PASSWORD' : u'',
+ 'IDENTICA_KEY' : u'',
+ 'IDENTICA_SECRET' : u'',
+ 'GOOGLE_ANALYTICS_KEY' : u'',
+ 'GOOGLE_SITEMAP_CODE' : u'',
+ 'LDAP_PROVIDER_NAME' : u'',
+ 'LDAP_URL' : u'',
+ 'LINKEDIN_KEY' : u'',
+ 'LINKEDIN_SECRET' : u'',
+ 'TWITTER_KEY' : u'',
+ 'TWITTER_SECRET' : u'',
+ 'USE_LDAP_FOR_PASSWORD_LOGIN' : False,
+ 'USE_RECAPTCHA' : False,
+ },
+ 'FLATPAGES' : {
+ 'FORUM_ABOUT' : u'',
+ 'FORUM_FAQ' : u'',
+ 'FORUM_PRIVACY' : u'',
+ },
+ 'FORUM_DATA_RULES' : {
+ 'MIN_TITLE_LENGTH' : 1,
+ 'MIN_QUESTION_BODY_LENGTH' : 1,
+ 'MIN_ANSWER_BODY_LENGTH' : 1,
+ 'WIKI_ON' : True,
+ 'ALLOW_ASK_ANONYMOUSLY' : True,
+ 'ALLOW_POSTING_BEFORE_LOGGING_IN' : True,
+ 'ALLOW_SWAPPING_QUESTION_WITH_ANSWER' : False,
+ 'MAX_TAG_LENGTH' : 20,
+ 'MIN_TITLE_LENGTH' : 1,
+ 'MIN_QUESTION_BODY_LENGTH' : 1,
+ 'MIN_ANSWER_BODY_LENGTH' : 1,
+ 'MANDATORY_TAGS' : u'',
+ 'FORCE_LOWERCASE_TAGS' : False,
+ 'TAG_LIST_FORMAT' : u'list',
+ 'USE_WILDCARD_TAGS' : False,
+ 'MAX_COMMENTS_TO_SHOW' : 5,
+ 'MAX_COMMENT_LENGTH' : 300,
+ 'USE_TIME_LIMIT_TO_EDIT_COMMENT' : True,
+ 'MINUTES_TO_EDIT_COMMENT' : 10,
+ 'SAVE_COMMENT_ON_ENTER' : True,
+ 'MIN_SEARCH_WORD_LENGTH' : 4,
+ 'DECOUPLE_TEXT_QUERY_FROM_SEARCH_STATE' : False,
+ 'MAX_TAGS_PER_POST' : 5,
+ 'DEFAULT_QUESTIONS_PAGE_SIZE' : u'30',
+ 'UNANSWERED_QUESTION_MEANING' : u'NO_ACCEPTED_ANSWERS',
+
+ # Enabling video requires forked version of markdown
+ # pip uninstall markdown2
+ # pip install -e git+git://github.com/andryuha/python-markdown2.git#egg=markdown2
+ 'ENABLE_VIDEO_EMBEDDING' : False,
+ },
+ 'GENERAL_SKIN_SETTINGS' : {
+ 'CUSTOM_CSS' : u'',
+ 'CUSTOM_FOOTER' : u'',
+ 'CUSTOM_HEADER' : u'',
+ 'CUSTOM_HTML_HEAD' : u'',
+ 'CUSTOM_JS' : u'',
+ 'SITE_FAVICON' : u'/images/favicon.gif',
+ 'SITE_LOGO_URL' : u'/images/logo.gif',
+ 'SHOW_LOGO' : False,
+ 'LOCAL_LOGIN_ICON' : u'/images/pw-login.gif',
+ 'ALWAYS_SHOW_ALL_UI_FUNCTIONS' : False,
+ 'ASKBOT_DEFAULT_SKIN' : u'default',
+ 'USE_CUSTOM_HTML_HEAD' : False,
+ 'FOOTER_MODE' : u'default',
+ 'USE_CUSTOM_CSS' : False,
+ 'USE_CUSTOM_JS' : False,
+ },
+ 'LEADING_SIDEBAR' : {
+ 'ENABLE_LEADING_SIDEBAR' : False,
+ 'LEADING_SIDEBAR' : u'',
+ },
+ 'LOGIN_PROVIDERS' : {
+ 'PASSWORD_REGISTER_SHOW_PROVIDER_BUTTONS' : True,
+ 'SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN' : True,
+ 'SIGNIN_AOL_ENABLED' : True,
+ 'SIGNIN_BLOGGER_ENABLED' : True,
+ 'SIGNIN_CLAIMID_ENABLED' : True,
+ 'SIGNIN_FACEBOOK_ENABLED' : True,
+ 'SIGNIN_FLICKR_ENABLED' : True,
+ 'SIGNIN_GOOGLE_ENABLED' : True,
+ 'SIGNIN_IDENTI.CA_ENABLED' : True,
+ 'SIGNIN_LINKEDIN_ENABLED' : True,
+ 'SIGNIN_LIVEJOURNAL_ENABLED' : True,
+ 'SIGNIN_LOCAL_ENABLED' : True,
+ 'SIGNIN_OPENID_ENABLED' : True,
+ 'SIGNIN_TECHNORATI_ENABLED' : True,
+ 'SIGNIN_TWITTER_ENABLED' : True,
+ 'SIGNIN_VERISIGN_ENABLED' : True,
+ 'SIGNIN_VIDOOP_ENABLED' : True,
+ 'SIGNIN_WORDPRESS_ENABLED' : True,
+ 'SIGNIN_WORDPRESS_SITE_ENABLED' : False,
+ 'SIGNIN_YAHOO_ENABLED' : True,
+ 'WORDPRESS_SITE_ICON' : u'/images/logo.gif',
+ 'WORDPRESS_SITE_URL' : '',
+ },
+ 'LICENSE_SETTINGS' : {
+ 'LICENSE_ACRONYM' : u'cc-by-sa',
+ 'LICENSE_LOGO_URL' : u'/images/cc-by-sa.png',
+ 'LICENSE_TITLE' : u'Creative Commons Attribution Share Alike 3.0',
+ 'LICENSE_URL' : 'http://creativecommons.org/licenses/by-sa/3.0/legalcode',
+ 'LICENSE_USE_LOGO' : True,
+ 'LICENSE_USE_URL' : True,
+ 'USE_LICENSE' : True,
+ },
+ 'MARKUP' : {
+ 'MARKUP_CODE_FRIENDLY' : False,
+ 'ENABLE_MATHJAX' : False, # FIXME: Test with this enabled
+ 'MATHJAX_BASE_URL' : u'',
+ 'ENABLE_AUTO_LINKING' : False,
+ 'AUTO_LINK_PATTERNS' : u'',
+ 'AUTO_LINK_URLS' : u'',
+ },
+ 'MIN_REP' : {
+ 'MIN_REP_TO_ACCEPT_OWN_ANSWER' : 1,
+ 'MIN_REP_TO_ANSWER_OWN_QUESTION' : 1,
+ 'MIN_REP_TO_CLOSE_OTHERS_QUESTIONS' : 100,
+ 'MIN_REP_TO_CLOSE_OWN_QUESTIONS' : 1,
+ 'MIN_REP_TO_DELETE_OTHERS_COMMENTS' : 2000,
+ 'MIN_REP_TO_DELETE_OTHERS_POSTS' : 5000,
+ 'MIN_REP_TO_EDIT_OTHERS_POSTS' : 2000,
+ 'MIN_REP_TO_EDIT_WIKI' : 1,
+ 'MIN_REP_TO_FLAG_OFFENSIVE' : 1,
+ 'MIN_REP_TO_HAVE_STRONG_URL' : 250,
+ 'MIN_REP_TO_LEAVE_COMMENTS' : 1,
+ 'MIN_REP_TO_LOCK_POSTS' : 4000,
+ 'MIN_REP_TO_REOPEN_OWN_QUESTIONS' : 1,
+ 'MIN_REP_TO_RETAG_OTHERS_QUESTIONS' : 1,
+ 'MIN_REP_TO_UPLOAD_FILES' : 1,
+ 'MIN_REP_TO_VIEW_OFFENSIVE_FLAGS' : 2000,
+ 'MIN_REP_TO_VOTE_DOWN' : 1,
+ 'MIN_REP_TO_VOTE_UP' : 1,
+ },
+ 'QA_SITE_SETTINGS' : {
+ 'APP_COPYRIGHT' : u'Copyright Askbot, 2010-2011.',
+ 'APP_DESCRIPTION' : u'Open source question and answer forum written in Python and Django',
+ 'APP_KEYWORDS' : u'Askbot,CNPROG,forum,community',
+ 'APP_SHORT_NAME' : u'Askbot',
+ 'APP_TITLE' : u'Askbot: Open Source Q&A Forum',
+ 'APP_URL' : u'http://askbot.org',
+ 'FEEDBACK_SITE_URL' : u'',
+ 'ENABLE_GREETING_FOR_ANON_USER' : True,
+ 'GREETING_FOR_ANONYMOUS_USER' : u'First time here? Check out the FAQ!',
+ },
+ 'REP_CHANGES' : {
+ 'MAX_REP_GAIN_PER_USER_PER_DAY' : 200,
+ 'REP_GAIN_FOR_ACCEPTING_ANSWER' : 2,
+ 'REP_GAIN_FOR_CANCELING_DOWNVOTE' : 1,
+ 'REP_GAIN_FOR_RECEIVING_ANSWER_ACCEPTANCE' : 15,
+ 'REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION' : 2,
+ 'REP_GAIN_FOR_RECEIVING_UPVOTE' : 10,
+ 'REP_LOSS_FOR_CANCELING_ANSWER_ACCEPTANCE' : -2,
+ 'REP_LOSS_FOR_DOWNVOTING' : -2,
+ 'REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE' : -5,
+ 'REP_LOSS_FOR_RECEIVING_DOWNVOTE' : -1,
+ 'REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION' : -100,
+ 'REP_LOSS_FOR_RECEIVING_FLAG' : -2,
+ 'REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION' : -30,
+ 'REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION' : -10,
+ },
+ 'SOCIAL_SHARING' : {
+ 'ENABLE_SHARING_TWITTER' : False,
+ 'ENABLE_SHARING_FACEBOOK' : False,
+ 'ENABLE_SHARING_LINKEDIN' : False,
+ 'ENABLE_SHARING_IDENTICA' : False,
+ 'ENABLE_SHARING_GOOGLE' : False,
+ },
+ 'SIDEBAR_MAIN' : {
+ 'SIDEBAR_MAIN_AVATAR_LIMIT' : 16,
+ 'SIDEBAR_MAIN_FOOTER' : u'',
+ 'SIDEBAR_MAIN_HEADER' : u'',
+ 'SIDEBAR_MAIN_SHOW_AVATARS' : True,
+ 'SIDEBAR_MAIN_SHOW_TAGS' : True,
+ 'SIDEBAR_MAIN_SHOW_TAG_SELECTOR' : True,
+ },
+ 'SIDEBAR_PROFILE' : {
+ 'SIDEBAR_PROFILE_FOOTER' : u'',
+ 'SIDEBAR_PROFILE_HEADER' : u'',
+ },
+ 'SIDEBAR_QUESTION' : {
+ 'SIDEBAR_QUESTION_FOOTER' : u'',
+ 'SIDEBAR_QUESTION_HEADER' : u'',
+ 'SIDEBAR_QUESTION_SHOW_META' : True,
+ 'SIDEBAR_QUESTION_SHOW_RELATED' : True,
+ 'SIDEBAR_QUESTION_SHOW_TAGS' : True,
+ },
+ 'SITE_MODES' : {
+ 'ACTIVATE_BOOTSTRAP_MODE' : False,
+ },
+ 'SKIN_COUNTER_SETTINGS' : {
+
+ },
+ 'SPAM_AND_MODERATION' : {
+ 'AKISMET_API_KEY' : u'',
+ 'USE_AKISMET' : False,
+ },
+ 'USER_SETTINGS' : {
+ 'EDITABLE_SCREEN_NAME' : False,
+ 'EDITABLE_EMAIL' : False,
+ 'ALLOW_ADD_REMOVE_LOGIN_METHODS' : False,
+ 'ENABLE_GRAVATAR' : False,
+ 'GRAVATAR_TYPE' : u'identicon',
+ 'NAME_OF_ANONYMOUS_USER' : u'',
+ 'DEFAULT_AVATAR_URL' : u'/images/nophoto.png',
+ 'MIN_USERNAME_LENGTH' : 1,
+ 'ALLOW_ACCOUNT_RECOVERY_BY_EMAIL' : True,
+ },
+ 'VOTE_RULES' : {
+ 'MAX_VOTES_PER_USER_PER_DAY' : 30,
+ 'MAX_FLAGS_PER_USER_PER_DAY' : 5,
+ 'MIN_DAYS_FOR_STAFF_TO_ACCEPT_ANSWER' : 7,
+ 'MIN_DAYS_TO_ANSWER_OWN_QUESTION' : 0,
+ 'MIN_FLAGS_TO_DELETE_POST' : 5,
+ 'MIN_FLAGS_TO_HIDE_POST' : 3,
+ 'MAX_DAYS_TO_CANCEL_VOTE' : 1,
+ 'VOTES_LEFT_WARNING_THRESHOLD' : 5,
+ },
+ },
+ },
+}
+
+# Celery Settings
+BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport"
+CELERY_ALWAYS_EAGER = True
+
+ot = MAKO_TEMPLATES
+MAKO_TEMPLATES['course'] = [DATA_DIR]
+MAKO_TEMPLATES['sections'] = [DATA_DIR+'/sections']
+MAKO_TEMPLATES['custom_tags'] = [DATA_DIR+'/custom_tags']
+MAKO_TEMPLATES['main'] = [BASE_DIR+'/templates/']
+
+
+MAKO_TEMPLATES.update(ot)
+
+if MAKO_MODULE_DIR == None:
+ MAKO_MODULE_DIR = tempfile.mkdtemp('mako')
+
+djcelery.setup_loader()
+
diff --git a/settings2/README.txt b/settings2/README.txt
new file mode 100644
index 0000000000..64a4c910df
--- /dev/null
+++ b/settings2/README.txt
@@ -0,0 +1,4 @@
+Transitional for moving to new settings scheme.
+
+To use:
+django-admin runserver --settings=settings2.dev --pythonpath="."
diff --git a/settings2/__init__.py b/settings2/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/settings2/askbotsettings.py b/settings2/askbotsettings.py
new file mode 100644
index 0000000000..8b04726a00
--- /dev/null
+++ b/settings2/askbotsettings.py
@@ -0,0 +1,287 @@
+# askbot livesettings
+LIVESETTINGS_OPTIONS = {
+ 1: {
+ 'DB' : False,
+ 'SETTINGS' : {
+ 'ACCESS_CONTROL' : {
+ 'ASKBOT_CLOSED_FORUM_MODE' : True,
+ },
+ 'BADGES' : {
+ 'DISCIPLINED_BADGE_MIN_UPVOTES' : 3,
+ 'PEER_PRESSURE_BADGE_MIN_DOWNVOTES' : 3,
+ 'TEACHER_BADGE_MIN_UPVOTES' : 1,
+ 'NICE_ANSWER_BADGE_MIN_UPVOTES' : 2,
+ 'GOOD_ANSWER_BADGE_MIN_UPVOTES' : 3,
+ 'GREAT_ANSWER_BADGE_MIN_UPVOTES' : 5,
+ 'NICE_QUESTION_BADGE_MIN_UPVOTES' : 2,
+ 'GOOD_QUESTION_BADGE_MIN_UPVOTES' : 3,
+ 'GREAT_QUESTION_BADGE_MIN_UPVOTES' : 5,
+ 'POPULAR_QUESTION_BADGE_MIN_VIEWS' : 150,
+ 'NOTABLE_QUESTION_BADGE_MIN_VIEWS' : 250,
+ 'FAMOUS_QUESTION_BADGE_MIN_VIEWS' : 500,
+ 'SELF_LEARNER_BADGE_MIN_UPVOTES' : 1,
+ 'CIVIC_DUTY_BADGE_MIN_VOTES' : 100,
+ 'ENLIGHTENED_BADGE_MIN_UPVOTES' : 3,
+ 'ASSOCIATE_EDITOR_BADGE_MIN_EDITS' : 20,
+ 'COMMENTATOR_BADGE_MIN_COMMENTS' : 10,
+ 'ENTHUSIAST_BADGE_MIN_DAYS' : 30,
+ 'FAVORITE_QUESTION_BADGE_MIN_STARS' : 3,
+ 'GURU_BADGE_MIN_UPVOTES' : 5,
+ 'NECROMANCER_BADGE_MIN_DELAY' : 30,
+ 'NECROMANCER_BADGE_MIN_UPVOTES' : 1,
+ 'STELLAR_QUESTION_BADGE_MIN_STARS' : 5,
+ 'TAXONOMIST_BADGE_MIN_USE_COUNT' : 10,
+ },
+ 'EMAIL' : {
+ 'EMAIL_SUBJECT_PREFIX' : u'[Django] ',
+ 'EMAIL_UNIQUE' : True,
+ 'EMAIL_VALIDATION' : False,
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_M_AND_C' : u'w',
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ALL' : u'w',
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ANS' : u'w',
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ASK' : u'w',
+ 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_SEL' : u'w',
+ 'ENABLE_UNANSWERED_REMINDERS' : False,
+ 'DAYS_BEFORE_SENDING_UNANSWERED_REMINDER' : 1,
+ 'UNANSWERED_REMINDER_FREQUENCY' : 1,
+ 'MAX_UNANSWERED_REMINDERS' : 5,
+ 'ENABLE_ACCEPT_ANSWER_REMINDERS' : False,
+ 'DAYS_BEFORE_SENDING_ACCEPT_ANSWER_REMINDER' : 3,
+ 'ACCEPT_ANSWER_REMINDER_FREQUENCY' : 3,
+ 'MAX_ACCEPT_ANSWER_REMINDERS' : 5,
+ 'ANONYMOUS_USER_EMAIL' : u'anonymous@askbot.org',
+ 'ALLOW_ASKING_BY_EMAIL' : False,
+ 'REPLACE_SPACE_WITH_DASH_IN_EMAILED_TAGS' : True,
+ 'MAX_ALERTS_PER_EMAIL' : 7,
+ },
+ 'EMBEDDABLE_WIDGETS' : {
+ 'QUESTIONS_WIDGET_CSS' : u"\nbody {\n overflow: hidden;\n}\n#container {\n width: 200px;\n height: 350px;\n}\nul {\n list-style: none;\n padding: 5px;\n margin: 5px;\n}\nli {\n border-bottom: #CCC 1px solid;\n padding-bottom: 5px;\n padding-top: 5px;\n}\nli:last-child {\n border: none;\n}\na {\n text-decoration: none;\n color: #464646;\n font-family: 'Yanone Kaffeesatz', sans-serif;\n font-size: 15px;\n}\n",
+ 'QUESTIONS_WIDGET_FOOTER' : u"\n\n",
+ 'QUESTIONS_WIDGET_HEADER' : u'',
+ 'QUESTIONS_WIDGET_MAX_QUESTIONS' : 7,
+ },
+ 'EXTERNAL_KEYS' : {
+ 'RECAPTCHA_KEY' : u'',
+ 'RECAPTCHA_SECRET' : u'',
+ 'FACEBOOK_KEY' : u'',
+ 'FACEBOOK_SECRET' : u'',
+ 'HOW_TO_CHANGE_LDAP_PASSWORD' : u'',
+ 'IDENTICA_KEY' : u'',
+ 'IDENTICA_SECRET' : u'',
+ 'GOOGLE_ANALYTICS_KEY' : u'',
+ 'GOOGLE_SITEMAP_CODE' : u'',
+ 'LDAP_PROVIDER_NAME' : u'',
+ 'LDAP_URL' : u'',
+ 'LINKEDIN_KEY' : u'',
+ 'LINKEDIN_SECRET' : u'',
+ 'TWITTER_KEY' : u'',
+ 'TWITTER_SECRET' : u'',
+ 'USE_LDAP_FOR_PASSWORD_LOGIN' : False,
+ 'USE_RECAPTCHA' : False,
+ },
+ 'FLATPAGES' : {
+ 'FORUM_ABOUT' : u'',
+ 'FORUM_FAQ' : u'',
+ 'FORUM_PRIVACY' : u'',
+ },
+ 'FORUM_DATA_RULES' : {
+ 'MIN_TITLE_LENGTH' : 1,
+ 'MIN_QUESTION_BODY_LENGTH' : 1,
+ 'MIN_ANSWER_BODY_LENGTH' : 1,
+ 'WIKI_ON' : True,
+ 'ALLOW_ASK_ANONYMOUSLY' : True,
+ 'ALLOW_POSTING_BEFORE_LOGGING_IN' : True,
+ 'ALLOW_SWAPPING_QUESTION_WITH_ANSWER' : False,
+ 'MAX_TAG_LENGTH' : 20,
+ 'MIN_TITLE_LENGTH' : 1,
+ 'MIN_QUESTION_BODY_LENGTH' : 1,
+ 'MIN_ANSWER_BODY_LENGTH' : 1,
+ 'MANDATORY_TAGS' : u'',
+ 'FORCE_LOWERCASE_TAGS' : False,
+ 'TAG_LIST_FORMAT' : u'list',
+ 'USE_WILDCARD_TAGS' : False,
+ 'MAX_COMMENTS_TO_SHOW' : 5,
+ 'MAX_COMMENT_LENGTH' : 300,
+ 'USE_TIME_LIMIT_TO_EDIT_COMMENT' : True,
+ 'MINUTES_TO_EDIT_COMMENT' : 10,
+ 'SAVE_COMMENT_ON_ENTER' : True,
+ 'MIN_SEARCH_WORD_LENGTH' : 4,
+ 'DECOUPLE_TEXT_QUERY_FROM_SEARCH_STATE' : False,
+ 'MAX_TAGS_PER_POST' : 5,
+ 'DEFAULT_QUESTIONS_PAGE_SIZE' : u'30',
+ 'UNANSWERED_QUESTION_MEANING' : u'NO_ACCEPTED_ANSWERS',
+
+ # Enabling video requires forked version of markdown
+ # pip uninstall markdown2
+ # pip install -e git+git://github.com/andryuha/python-markdown2.git#egg=markdown2
+ 'ENABLE_VIDEO_EMBEDDING' : False,
+ },
+ 'GENERAL_SKIN_SETTINGS' : {
+ 'CUSTOM_CSS' : u'',
+ 'CUSTOM_FOOTER' : u'',
+ 'CUSTOM_HEADER' : u'',
+ 'CUSTOM_HTML_HEAD' : u'',
+ 'CUSTOM_JS' : u'',
+ 'SITE_FAVICON' : u'/images/favicon.gif',
+ 'SITE_LOGO_URL' : u'/images/logo.gif',
+ 'SHOW_LOGO' : False,
+ 'LOCAL_LOGIN_ICON' : u'/images/pw-login.gif',
+ 'ALWAYS_SHOW_ALL_UI_FUNCTIONS' : False,
+ 'ASKBOT_DEFAULT_SKIN' : u'default',
+ 'USE_CUSTOM_HTML_HEAD' : False,
+ 'FOOTER_MODE' : u'default',
+ 'USE_CUSTOM_CSS' : False,
+ 'USE_CUSTOM_JS' : False,
+ },
+ 'LEADING_SIDEBAR' : {
+ 'ENABLE_LEADING_SIDEBAR' : False,
+ 'LEADING_SIDEBAR' : u'',
+ },
+ 'LOGIN_PROVIDERS' : {
+ 'PASSWORD_REGISTER_SHOW_PROVIDER_BUTTONS' : True,
+ 'SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN' : True,
+ 'SIGNIN_AOL_ENABLED' : True,
+ 'SIGNIN_BLOGGER_ENABLED' : True,
+ 'SIGNIN_CLAIMID_ENABLED' : True,
+ 'SIGNIN_FACEBOOK_ENABLED' : True,
+ 'SIGNIN_FLICKR_ENABLED' : True,
+ 'SIGNIN_GOOGLE_ENABLED' : True,
+ 'SIGNIN_IDENTI.CA_ENABLED' : True,
+ 'SIGNIN_LINKEDIN_ENABLED' : True,
+ 'SIGNIN_LIVEJOURNAL_ENABLED' : True,
+ 'SIGNIN_LOCAL_ENABLED' : True,
+ 'SIGNIN_OPENID_ENABLED' : True,
+ 'SIGNIN_TECHNORATI_ENABLED' : True,
+ 'SIGNIN_TWITTER_ENABLED' : True,
+ 'SIGNIN_VERISIGN_ENABLED' : True,
+ 'SIGNIN_VIDOOP_ENABLED' : True,
+ 'SIGNIN_WORDPRESS_ENABLED' : True,
+ 'SIGNIN_WORDPRESS_SITE_ENABLED' : False,
+ 'SIGNIN_YAHOO_ENABLED' : True,
+ 'WORDPRESS_SITE_ICON' : u'/images/logo.gif',
+ 'WORDPRESS_SITE_URL' : '',
+ },
+ 'LICENSE_SETTINGS' : {
+ 'LICENSE_ACRONYM' : u'cc-by-sa',
+ 'LICENSE_LOGO_URL' : u'/images/cc-by-sa.png',
+ 'LICENSE_TITLE' : u'Creative Commons Attribution Share Alike 3.0',
+ 'LICENSE_URL' : 'http://creativecommons.org/licenses/by-sa/3.0/legalcode',
+ 'LICENSE_USE_LOGO' : True,
+ 'LICENSE_USE_URL' : True,
+ 'USE_LICENSE' : True,
+ },
+ 'MARKUP' : {
+ 'MARKUP_CODE_FRIENDLY' : False,
+ 'ENABLE_MATHJAX' : False, # FIXME: Test with this enabled
+ 'MATHJAX_BASE_URL' : u'',
+ 'ENABLE_AUTO_LINKING' : False,
+ 'AUTO_LINK_PATTERNS' : u'',
+ 'AUTO_LINK_URLS' : u'',
+ },
+ 'MIN_REP' : {
+ 'MIN_REP_TO_ACCEPT_OWN_ANSWER' : 1,
+ 'MIN_REP_TO_ANSWER_OWN_QUESTION' : 1,
+ 'MIN_REP_TO_CLOSE_OTHERS_QUESTIONS' : 100,
+ 'MIN_REP_TO_CLOSE_OWN_QUESTIONS' : 1,
+ 'MIN_REP_TO_DELETE_OTHERS_COMMENTS' : 2000,
+ 'MIN_REP_TO_DELETE_OTHERS_POSTS' : 5000,
+ 'MIN_REP_TO_EDIT_OTHERS_POSTS' : 2000,
+ 'MIN_REP_TO_EDIT_WIKI' : 1,
+ 'MIN_REP_TO_FLAG_OFFENSIVE' : 1,
+ 'MIN_REP_TO_HAVE_STRONG_URL' : 250,
+ 'MIN_REP_TO_LEAVE_COMMENTS' : 1,
+ 'MIN_REP_TO_LOCK_POSTS' : 4000,
+ 'MIN_REP_TO_REOPEN_OWN_QUESTIONS' : 1,
+ 'MIN_REP_TO_RETAG_OTHERS_QUESTIONS' : 1,
+ 'MIN_REP_TO_UPLOAD_FILES' : 1,
+ 'MIN_REP_TO_VIEW_OFFENSIVE_FLAGS' : 2000,
+ 'MIN_REP_TO_VOTE_DOWN' : 1,
+ 'MIN_REP_TO_VOTE_UP' : 1,
+ },
+ 'QA_SITE_SETTINGS' : {
+ 'APP_COPYRIGHT' : u'Copyright Askbot, 2010-2011.',
+ 'APP_DESCRIPTION' : u'Open source question and answer forum written in Python and Django',
+ 'APP_KEYWORDS' : u'Askbot,CNPROG,forum,community',
+ 'APP_SHORT_NAME' : u'Askbot',
+ 'APP_TITLE' : u'Askbot: Open Source Q&A Forum',
+ 'APP_URL' : u'http://askbot.org',
+ 'FEEDBACK_SITE_URL' : u'',
+ 'ENABLE_GREETING_FOR_ANON_USER' : True,
+ 'GREETING_FOR_ANONYMOUS_USER' : u'First time here? Check out the FAQ!',
+ },
+ 'REP_CHANGES' : {
+ 'MAX_REP_GAIN_PER_USER_PER_DAY' : 200,
+ 'REP_GAIN_FOR_ACCEPTING_ANSWER' : 2,
+ 'REP_GAIN_FOR_CANCELING_DOWNVOTE' : 1,
+ 'REP_GAIN_FOR_RECEIVING_ANSWER_ACCEPTANCE' : 15,
+ 'REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION' : 2,
+ 'REP_GAIN_FOR_RECEIVING_UPVOTE' : 10,
+ 'REP_LOSS_FOR_CANCELING_ANSWER_ACCEPTANCE' : -2,
+ 'REP_LOSS_FOR_DOWNVOTING' : -2,
+ 'REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE' : -5,
+ 'REP_LOSS_FOR_RECEIVING_DOWNVOTE' : -1,
+ 'REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION' : -100,
+ 'REP_LOSS_FOR_RECEIVING_FLAG' : -2,
+ 'REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION' : -30,
+ 'REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION' : -10,
+ },
+ 'SOCIAL_SHARING' : {
+ 'ENABLE_SHARING_TWITTER' : False,
+ 'ENABLE_SHARING_FACEBOOK' : False,
+ 'ENABLE_SHARING_LINKEDIN' : False,
+ 'ENABLE_SHARING_IDENTICA' : False,
+ 'ENABLE_SHARING_GOOGLE' : False,
+ },
+ 'SIDEBAR_MAIN' : {
+ 'SIDEBAR_MAIN_AVATAR_LIMIT' : 16,
+ 'SIDEBAR_MAIN_FOOTER' : u'',
+ 'SIDEBAR_MAIN_HEADER' : u'',
+ 'SIDEBAR_MAIN_SHOW_AVATARS' : True,
+ 'SIDEBAR_MAIN_SHOW_TAGS' : True,
+ 'SIDEBAR_MAIN_SHOW_TAG_SELECTOR' : True,
+ },
+ 'SIDEBAR_PROFILE' : {
+ 'SIDEBAR_PROFILE_FOOTER' : u'',
+ 'SIDEBAR_PROFILE_HEADER' : u'',
+ },
+ 'SIDEBAR_QUESTION' : {
+ 'SIDEBAR_QUESTION_FOOTER' : u'',
+ 'SIDEBAR_QUESTION_HEADER' : u'',
+ 'SIDEBAR_QUESTION_SHOW_META' : True,
+ 'SIDEBAR_QUESTION_SHOW_RELATED' : True,
+ 'SIDEBAR_QUESTION_SHOW_TAGS' : True,
+ },
+ 'SITE_MODES' : {
+ 'ACTIVATE_BOOTSTRAP_MODE' : False,
+ },
+ 'SKIN_COUNTER_SETTINGS' : {
+
+ },
+ 'SPAM_AND_MODERATION' : {
+ 'AKISMET_API_KEY' : u'',
+ 'USE_AKISMET' : False,
+ },
+ 'USER_SETTINGS' : {
+ 'EDITABLE_SCREEN_NAME' : False,
+ 'EDITABLE_EMAIL' : False,
+ 'ALLOW_ADD_REMOVE_LOGIN_METHODS' : False,
+ 'ENABLE_GRAVATAR' : False,
+ 'GRAVATAR_TYPE' : u'identicon',
+ 'NAME_OF_ANONYMOUS_USER' : u'',
+ 'DEFAULT_AVATAR_URL' : u'/images/nophoto.png',
+ 'MIN_USERNAME_LENGTH' : 1,
+ 'ALLOW_ACCOUNT_RECOVERY_BY_EMAIL' : True,
+ },
+ 'VOTE_RULES' : {
+ 'MAX_VOTES_PER_USER_PER_DAY' : 30,
+ 'MAX_FLAGS_PER_USER_PER_DAY' : 5,
+ 'MIN_DAYS_FOR_STAFF_TO_ACCEPT_ANSWER' : 7,
+ 'MIN_DAYS_TO_ANSWER_OWN_QUESTION' : 0,
+ 'MIN_FLAGS_TO_DELETE_POST' : 5,
+ 'MIN_FLAGS_TO_HIDE_POST' : 3,
+ 'MAX_DAYS_TO_CANCEL_VOTE' : 1,
+ 'VOTES_LEFT_WARNING_THRESHOLD' : 5,
+ },
+ },
+ },
+}
diff --git a/settings2/aws.py b/settings2/aws.py
new file mode 100644
index 0000000000..138f52c255
--- /dev/null
+++ b/settings2/aws.py
@@ -0,0 +1,18 @@
+from common import *
+
+EMAIL_BACKEND = 'django_ses.SESBackend'
+SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
+DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
+
+CSRF_COOKIE_DOMAIN = '.mitx.mit.edu'
+LIB_URL = 'https://mitxstatic.s3.amazonaws.com/js/'
+BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/'
+
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+ }
+}
+
+DEBUG = False
+TEMPLATE_DEBUG = False
diff --git a/settings2/common.py b/settings2/common.py
new file mode 100644
index 0000000000..5d5dffb084
--- /dev/null
+++ b/settings2/common.py
@@ -0,0 +1,348 @@
+"""
+This is the common settings file, intended to set sane defaults. If you have a
+piece of configuration that's dependent on a set of feature flags being set,
+then create a function that returns the calculated value based on the value of
+MITX_FEATURES[...]. That classes that extend this one can change the feature
+configuration in an environment specific config file and re-calculate those
+values.
+
+We should make a method that calls all these config methods so that you just
+make one call at the end of your site-specific dev file and it reset all the
+dependent variables (like INSTALLED_APPS) for you.
+
+TODO:
+1. Right now our treatment of static content in general and in particular
+ course-specific static content is haphazard.
+2. We should have a more disciplined approach to feature flagging, even if it
+ just means that we stick them in a dict called MITX_FEATURES.
+3. We need to handle configuration for multiple courses. This could be as
+ multiple sites, but we do need a way to map their data assets.
+"""
+import os
+import platform
+import sys
+import tempfile
+
+import djcelery
+from path import path
+
+from askbotsettings import LIVESETTINGS_OPTIONS
+
+################################### FEATURES ###################################
+COURSEWARE_ENABLED = True
+ASKBOT_ENABLED = True
+GENERATE_RANDOM_USER_CREDENTIALS = False
+PERFSTATS = False
+
+# Features
+MITX_FEATURES = {
+ 'SAMPLE' : False
+}
+
+############################# SET PATH INFORMATION #############################
+PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitxweb
+ENV_ROOT = PROJECT_ROOT.dirname() # virtualenv dir /mitxweb is in
+#ASKBOT_ROOT = ENV_ROOT / "3rdparty" / "askbot-devel"
+ASKBOT_ROOT = ENV_ROOT / "askbot-devel"
+#COURSES_ROOT = ENV_ROOT / "courses"
+COURSES_ROOT = ENV_ROOT / "data"
+
+# FIXME: code shouldn't expect trailing "/"
+# FIXME: To support multiple courses, we should walk the courses dir at startup
+#DATA_DIR = COURSES_ROOT / "6002x" / ""
+DATA_DIR = COURSES_ROOT
+
+#print DATA_DIR, COURSES_ROOT, ASKBOT_ROOT, ENV_ROOT, PROJECT_ROOT
+
+sys.path.append(ENV_ROOT)
+sys.path.append(ASKBOT_ROOT)
+sys.path.append(ASKBOT_ROOT / "askbot" / "deps")
+sys.path.append(PROJECT_ROOT / 'djangoapps')
+sys.path.append(PROJECT_ROOT / 'lib')
+
+################################## MITXWEB #####################################
+# This is where we stick our compiled template files
+MAKO_MODULE_DIR = tempfile.mkdtemp('mako')
+MAKO_TEMPLATES = {}
+MAKO_TEMPLATES['course'] = [DATA_DIR]
+MAKO_TEMPLATES['sections'] = [DATA_DIR+'/sections']
+MAKO_TEMPLATES['custom_tags'] = [DATA_DIR+'/custom_tags']
+MAKO_TEMPLATES['main'] = [ENV_ROOT+'/templates/']
+
+TEXTBOOK_DIR = ENV_ROOT / "books" / "circuits_agarwal_lang"
+
+# FIXME ???????? --
+# We should have separate S3 staged URLs in case we need to make changes to
+# these assets and test them.
+LIB_URL = '/static/lib/'
+# LIB_URL = 'https://mitxstatic.s3.amazonaws.com/js/' # For AWS deploys
+
+# Dev machines shouldn't need the book
+# BOOK_URL = '/static/book/'
+BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/' # For AWS deploys
+
+# FIXME ??????? What are these exactly?
+# Configuration option for when we want to grab server error pages
+STATIC_GRAB = False
+DEV_CONTENT = True
+
+# FIXME: Should we be doing this truncation?
+TRACK_MAX_EVENT = 1000
+
+GENERATE_PROFILE_SCORES = False
+
+############################### DJANGO BUILT-INS ###############################
+# Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here
+DEBUG = False
+TEMPLATE_DEBUG = False
+
+# Site info
+SITE_ID = 1
+SITE_NAME = "localhost:8000"
+CSRF_COOKIE_DOMAIN = '127.0.0.1'
+HTTPS = 'on'
+#ROOT_URLCONF = 'mitxweb.urls'
+ROOT_URLCONF = 'mitx.urls'
+
+# Email
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+DEFAULT_FROM_EMAIL = 'registration@mitx.mit.edu'
+DEFAULT_FEEDBACK_EMAIL = 'feedback@mitx.mit.edu'
+ADMINS = (
+ ('MITx Admins', 'admin@mitx.mit.edu'),
+)
+MANAGERS = ADMINS
+
+# Static content
+STATIC_URL = '/static/'
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+STATIC_ROOT = ENV_ROOT / "staticfiles" # FIXME: Should this and uploads be moved out of the repo?
+
+# FIXME: We should iterate through the courses we have, adding the static
+# contents for each of them.
+STATICFILES_DIRS = (
+ # FIXME: Need to add entries for book, data/images, etc.
+# PROJECT_ROOT / "static",
+ ENV_ROOT / "static",
+ ASKBOT_ROOT / "askbot" / "skins",
+# ("circuits", DATA_DIR / "images"),
+# ("handouts", DATA_DIR / "handouts"),
+# ("subs", DATA_DIR / "subs"),
+# ("book", TEXTBOOK_DIR)
+)
+
+print STATICFILES_DIRS
+
+# Templates
+TEMPLATE_DIRS = (
+ ENV_ROOT / "templates",
+# PROJECT_ROOT / "templates",
+# DATA_DIR / "problems",
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ 'django.core.context_processors.request',
+ 'askbot.context.application_settings',
+ #'django.core.context_processors.i18n',
+ 'askbot.user_messages.context_processors.user_messages',#must be before auth
+ 'django.core.context_processors.auth', #this is required for admin
+ 'django.core.context_processors.csrf', #necessary for csrf protection
+)
+
+# Storage
+DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
+MEDIA_ROOT = ENV_ROOT / "uploads"
+MEDIA_URL = "/discussion/upfiles/"
+FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
+FILE_UPLOAD_HANDLERS = (
+ 'django.core.files.uploadhandler.MemoryFileUploadHandler',
+ 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
+)
+
+# Locale/Internationalization
+TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html
+USE_I18N = True
+USE_L10N = True
+
+################################### LOGGING ####################################
+# Might want to rewrite this to use logger code and push more things to the root
+# logger.
+pid = os.getpid() # So we can log which process is creating the log
+hostname = platform.node().split(".")[0]
+SYSLOG_ADDRESS = ('syslog.m.i4x.org', 514)
+
+handlers = ['console']
+# FIXME: re-enable syslogger later
+# if not DEBUG:
+# handlers.append('syslogger')
+
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': True,
+ 'formatters' : {
+ 'standard' : {
+ 'format' : '%(asctime)s %(levelname)s %(process)d [%(name)s] %(filename)s:%(lineno)d - %(message)s',
+ },
+ 'syslog_format' : {
+ 'format' : '[%(name)s] %(levelname)s [' + hostname + ' %(process)d] [%(filename)s:%(lineno)d] - %(message)s',
+ },
+ 'raw' : {
+ 'format' : '%(message)s',
+ }
+ },
+ 'handlers' : {
+ 'console' : {
+ 'level' : 'DEBUG',
+ 'class' : 'logging.StreamHandler',
+ 'formatter' : 'standard',
+ 'stream' : sys.stdout,
+ },
+ 'console_err' : {
+ 'level' : 'ERROR',
+ 'class' : 'logging.StreamHandler',
+ 'formatter' : 'standard',
+ 'stream' : sys.stderr,
+ },
+ 'syslogger' : {
+ 'level' : 'INFO',
+ 'class' : 'logging.handlers.SysLogHandler',
+ 'address' : SYSLOG_ADDRESS,
+ 'formatter' : 'syslog_format',
+ },
+ 'mail_admins' : {
+ 'level': 'ERROR',
+ 'class': 'django.utils.log.AdminEmailHandler',
+ },
+ },
+ 'loggers' : {
+ 'django' : {
+ 'handlers' : handlers + ['mail_admins'],
+ 'propagate' : True,
+ 'level' : 'INFO'
+ },
+ 'tracking' : {
+ 'handlers' : [] if DEBUG else ['syslogger'], # handlers,
+ 'level' : 'DEBUG',
+ 'propagate' : False,
+ },
+ 'root' : {
+ 'handlers' : handlers,
+ 'level' : 'DEBUG',
+ 'propagate' : False
+ },
+ 'mitx' : {
+ 'handlers' : handlers,
+ 'level' : 'DEBUG',
+ 'propagate' : False
+ },
+ }
+}
+
+#################################### AWS #######################################
+# S3BotoStorage insists on a timeout for uploaded assets. We should make it
+# permanent instead, but rather than trying to figure out exactly where that
+# setting is, I'm just bumping the expiration time to something absurd (100
+# years). This is only used if DEFAULT_FILE_STORAGE is overriden to use S3
+# in the global settings.py
+AWS_QUERYSTRING_EXPIRE = 10 * 365 * 24 * 60 * 60 # 10 years
+
+################################### ASKBOT #####################################
+ASKBOT_EXTRA_SKINS_DIR = ASKBOT_ROOT / "askbot" / "skins"
+ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
+ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 # result in bytes
+
+CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
+ASKBOT_URL = 'discussion/'
+LOGIN_REDIRECT_URL = '/'
+LOGIN_URL = '/'
+
+ALLOW_UNICODE_SLUGS = False
+ASKBOT_USE_STACKEXCHANGE_URLS = False # mimic url scheme of stackexchange
+ASKBOT_CSS_DEVEL = True
+
+# Celery Settings
+BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport"
+CELERY_ALWAYS_EAGER = True
+djcelery.setup_loader()
+
+################################# SIMPLEWIKI ###################################
+WIKI_REQUIRE_LOGIN_EDIT = True
+WIKI_REQUIRE_LOGIN_VIEW = True
+
+################################# 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',
+)
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+ 'askbot.skins.loaders.filesystem_load_template_source',
+ # 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'util.middleware.ExceptionLoggingMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'track.middleware.TrackMiddleware',
+ 'mitxmako.middleware.MakoMiddleware',
+ # 'debug_toolbar.middleware.DebugToolbarMiddleware',
+ 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware',
+ 'askbot.middleware.forum_mode.ForumModeMiddleware',
+ 'askbot.middleware.cancel.CancelActionMiddleware',
+ 'django.middleware.transaction.TransactionMiddleware',
+ 'askbot.middleware.view_log.ViewLogMiddleware',
+ 'askbot.middleware.spaceless.SpacelessMiddleware',
+ # 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
+)
+
+################################### APPS #######################################
+def installed_apps():
+ """If you want to get a different set of INSTALLED_APPS out of this, you'll
+ have to set ASKBOT_ENABLED and COURSEWARE_ENABLED to True/False and call
+ this method. We can't just take these as params because other pieces of the
+ code check fo the value of these constants.
+ """
+ # We always install these
+ STANDARD_APPS = ['django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.humanize',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'track',
+ 'util']
+ COURSEWARE_APPS = ['circuit',
+ 'courseware',
+ 'student',
+ 'static_template_view',
+ 'staticbook',
+ 'simplewiki',
+ 'perfstats']
+ ASKBOT_APPS = ['django.contrib.sitemaps',
+ 'django.contrib.admin',
+ 'south',
+ 'askbot.deps.livesettings',
+ 'askbot',
+ 'keyedcache',
+ 'robots',
+ 'django_countries',
+ 'djcelery',
+ 'djkombu',
+ 'followit']
+
+ return tuple(STANDARD_APPS +
+ (COURSEWARE_APPS if COURSEWARE_ENABLED else []) +
+ (ASKBOT_APPS if ASKBOT_ENABLED else []))
+
+INSTALLED_APPS = installed_apps()
diff --git a/settings2/dev.py b/settings2/dev.py
new file mode 100644
index 0000000000..925f9ac02f
--- /dev/null
+++ b/settings2/dev.py
@@ -0,0 +1,28 @@
+"""
+This config file runs the simplest dev environment using sqlite, and db-based
+sessions.
+"""
+from common import *
+
+CSRF_COOKIE_DOMAIN = 'localhost'
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': ENV_ROOT / "db" / "mitx.db",
+ }
+}
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
+
+DEBUG = True
+TEMPLATE_DEBUG = False
+
+# This is disabling ASKBOT, but not properly overwriting INSTALLED_APPS. ???
+# It's because our ASKBOT_ENABLED here is actually shadowing the real one.
+#
+# ASKBOT_ENABLED = True
+# MITX_FEATURES['SAMPLE'] = True # Switch to this system so we get around the shadowing
+#
+# INSTALLED_APPS = installed_apps()
diff --git a/settings2/devplus.py b/settings2/devplus.py
new file mode 100644
index 0000000000..9cc9a67779
--- /dev/null
+++ b/settings2/devplus.py
@@ -0,0 +1,35 @@
+"""
+This config file tries to mimic the production environment more closely than the
+normal dev.py. It assumes you're running a local instance of MySQL 5.1 and that
+you're running memcached.
+"""
+from common import *
+
+CSRF_COOKIE_DOMAIN = 'localhost'
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': 'wwc', # Or path to database file if using sqlite3.
+ 'USER': 'root', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '127.0.0.1', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '3306', # Set to empty string for default. Not used with sqlite3.
+ }
+}
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
+
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+ 'LOCATION': '127.0.0.1:11211',
+ }
+}
+
+
+SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
+
+DEBUG = True
+TEMPLATE_DEBUG = True
\ No newline at end of file
diff --git a/settings2/loadtest.py b/settings2/loadtest.py
new file mode 100644
index 0000000000..172fbcc125
--- /dev/null
+++ b/settings2/loadtest.py
@@ -0,0 +1,3 @@
+from common import *
+
+GENERATE_RANDOM_USER_CREDENTIALS = True
diff --git a/settings2/staging.py b/settings2/staging.py
new file mode 100644
index 0000000000..0c7dba41b6
--- /dev/null
+++ b/settings2/staging.py
@@ -0,0 +1,13 @@
+from aws import *
+
+# Staging specific overrides
+SITE_NAME = "staging.mitx.mit.edu"
+AWS_STORAGE_BUCKET_NAME = 'mitx_askbot_stage'
+CACHES['default']['LOCATION'] = ['***REMOVED***',
+ '***REMOVED***']
+
+### Secure Data Below Here ###
+SECRET_KEY = ""
+AWS_ACCESS_KEY_ID = ""
+AWS_SECRET_ACCESS_KEY = ""
+
diff --git a/settings_new_askbot.py b/settings_new_askbot.py
deleted file mode 100644
index 50a4929488..0000000000
--- a/settings_new_askbot.py
+++ /dev/null
@@ -1,392 +0,0 @@
-import os
-import platform
-import sys
-import tempfile
-
-import djcelery
-
-# Configuration option for when we want to grab server error pages
-STATIC_GRAB = False
-DEV_CONTENT = True
-
-LIB_URL = '/static/lib/'
-LIB_URL = 'https://mitxstatic.s3.amazonaws.com/js/'
-BOOK_URL = '/static/book/'
-BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/'
-
-# Feature Flags. These should be set to false until they are ready to deploy, and then eventually flag mechanisms removed
-GENERATE_PROFILE_SCORES = False # If this is true, random scores will be generated for the purpose of debugging the profile graphs
-
-# Our parent dir (mitx_all) is the BASE_DIR
-BASE_DIR = os.path.abspath(os.path.join(__file__, "..", ".."))
-
-COURSEWARE_ENABLED = True
-ASKBOT_ENABLED = True
-CSRF_COOKIE_DOMAIN = '127.0.0.1'
-
-# Defaults to be overridden
-EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
-SITE_NAME = "localhost:8000"
-
-DEFAULT_FROM_EMAIL = 'registration@mitx.mit.edu'
-DEFAULT_FEEDBACK_EMAIL = 'feedback@mitx.mit.edu'
-
-GENERATE_RANDOM_USER_CREDENTIALS = False
-
-WIKI_REQUIRE_LOGIN_EDIT = True
-WIKI_REQUIRE_LOGIN_VIEW = True
-
-PERFSTATS = False
-
-HTTPS = 'on'
-
-MEDIA_URL = ''
-MEDIA_ROOT = ''
-
-# S3BotoStorage insists on a timeout for uploaded assets. We should make it
-# permanent instead, but rather than trying to figure out exactly where that
-# setting is, I'm just bumping the expiration time to something absurd (100
-# years). This is only used if DEFAULT_FILE_STORAGE is overriden to use S3
-# in the global settings.py
-AWS_QUERYSTRING_EXPIRE = 10 * 365 * 24 * 60 * 60 # 10 years
-
-# Needed for Askbot
-# Deployed machines: Move to S3
-DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
-
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-
-ADMINS = (
- ('Piotr Mitros', 'staff@csail.mit.edu'),
-)
-
-MANAGERS = ADMINS
-
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-TIME_ZONE = 'America/New_York'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en'
-
-SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
-USE_I18N = True
-
-# If you set this to False, Django will not format dates, numbers and
-# calendars according to the current locale
-USE_L10N = True
-
-STATIC_URL = '/static/'
-
-# URL prefix for admin static files -- CSS, JavaScript and images.
-# Make sure to use a trailing slash.
-# Examples: "http://foo.com/static/admin/", "/static/admin/".
-ADMIN_MEDIA_PREFIX = '/static/admin/'
-
-# 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',
-# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
-)
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.Loader',
- 'django.template.loaders.app_directories.Loader',
-# 'django.template.loaders.eggs.Loader',
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'track.middleware.TrackMiddleware',
- 'mitxmako.middleware.MakoMiddleware',
- #'debug_toolbar.middleware.DebugToolbarMiddleware',
-)
-
-ROOT_URLCONF = 'mitx.urls'
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'courseware',
- 'student',
- 'django.contrib.humanize',
- 'static_template_view',
- 'staticbook',
- 'simplewiki',
- 'track',
- 'circuit',
- 'perfstats',
- 'util',
- # Uncomment the next line to enable the admin:
- # 'django.contrib.admin',
- # Uncomment the next line to enable admin documentation:
- # 'django.contrib.admindocs',
-)
-
-#TRACK_DIR = None
-DEBUG_TRACK_LOG = False
-# Maximum length of a tracking string. We don't want e.g. a file upload in our log
-TRACK_MAX_EVENT = 1000
-# Maximum length of log file before starting a new one.
-MAXLOG = 500
-
-LOG_DIR = "/tmp/"
-MAKO_MODULE_DIR = None
-
-# Make sure we execute correctly regardless of where we're called from
-execfile(os.path.join(BASE_DIR, "settings.py"))
-
-if MAKO_MODULE_DIR == None:
- MAKO_MODULE_DIR = tempfile.mkdtemp('mako')
-
-# A sample logging configuration. The only tangible logging
-# performed by this configuration is to send an email to
-# the site admins on every HTTP 500 error.
-# See http://docs.djangoproject.com/en/dev/topics/logging for
-# more details on how to customize your logging configuration.
-
-pid = os.getpid()
-hostname = platform.node().split(".")[0]
-SYSLOG_ADDRESS = ('syslog.m.i4x.org', 514)
-
-handlers = ['console']
-if not DEBUG:
- handlers.append('syslogger')
-
-LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': True,
- 'formatters' : {
- 'standard' : {
- 'format' : '%(asctime)s %(levelname)s %(process)d [%(name)s] %(filename)s:%(lineno)d - %(message)s',
- },
- 'syslog_format' : {
- 'format' : '[%(name)s] %(levelname)s [' + hostname + ' %(process)d] [%(filename)s:%(lineno)d] - %(message)s',
- },
- 'raw' : {
- 'format' : '%(message)s',
- }
- },
- 'handlers' : {
- 'console' : {
- 'level' : 'DEBUG' if DEBUG else 'INFO',
- 'class' : 'logging.StreamHandler',
- 'formatter' : 'standard',
- 'stream' : sys.stdout,
- },
- 'console_err' : {
- 'level' : 'ERROR',
- 'class' : 'logging.StreamHandler',
- 'formatter' : 'standard',
- 'stream' : sys.stderr,
- },
- 'syslogger' : {
- 'level' : 'INFO',
- 'class' : 'logging.handlers.SysLogHandler',
- 'address' : SYSLOG_ADDRESS,
- 'formatter' : 'syslog_format',
- },
- 'mail_admins' : {
- 'level': 'ERROR',
- 'class': 'django.utils.log.AdminEmailHandler',
- },
- },
- 'loggers' : {
- 'django' : {
- 'handlers' : handlers + ['mail_admins'],
- 'propagate' : True,
- 'level' : 'INFO'
- },
- 'tracking' : {
- 'handlers' : [] if DEBUG else ['syslogger'], # handlers,
- 'level' : 'DEBUG',
- 'propagate' : False,
- },
- 'root' : {
- 'handlers' : handlers,
- 'level' : 'DEBUG',
- 'propagate' : False
- },
- 'mitx' : {
- 'handlers' : handlers,
- 'level' : 'DEBUG',
- 'propagate' : False
- },
- }
-}
-
-
-
-if PERFSTATS :
- MIDDLEWARE_CLASSES = ( 'perfstats.middleware.ProfileMiddleware',) + MIDDLEWARE_CLASSES
-
-if 'TRACK_DIR' not in locals():
- TRACK_DIR = BASE_DIR+'/track_dir/'
-if 'STATIC_ROOT' not in locals():
- STATIC_ROOT = BASE_DIR+'/staticroot/'
-if 'DATA_DIR' not in locals():
- DATA_DIR = BASE_DIR+'/data/'
-if 'TEXTBOOK_DIR' not in locals():
- TEXTBOOK_DIR = BASE_DIR+'/textbook/'
-
-if 'TEMPLATE_DIRS' not in locals():
- TEMPLATE_DIRS = (
- BASE_DIR+'/templates/',
- DATA_DIR+'/templates',
- TEXTBOOK_DIR,
- )
-
-if 'STATICFILES_DIRS' not in locals():
- STATICFILES_DIRS = (
- BASE_DIR+'/3rdParty/static',
- BASE_DIR+'/static',
- )
-
-
-if 'ASKBOT_EXTRA_SKINS_DIR' not in locals():
- ASKBOT_EXTRA_SKINS_DIR = BASE_DIR+'/askbot-devel/askbot/skins'
-if 'ASKBOT_DIR' not in locals():
- ASKBOT_DIR = BASE_DIR+'/askbot-devel'
-
-sys.path.append(ASKBOT_DIR)
-import askbot
-import site
-
-STATICFILES_DIRS = STATICFILES_DIRS + ( ASKBOT_DIR+'/askbot/skins',)
-
-ASKBOT_ROOT = os.path.dirname(askbot.__file__)
-
-# Needed for Askbot
-# Deployed machines: Move to S3
-if MEDIA_ROOT == '':
- MEDIA_ROOT = ASKBOT_DIR+'/askbot/upfiles'
-if MEDIA_URL == '':
- MEDIA_URL = '/discussion/upfiles/'
-
-site.addsitedir(os.path.join(os.path.dirname(askbot.__file__), 'deps'))
-TEMPLATE_LOADERS = TEMPLATE_LOADERS + ('askbot.skins.loaders.filesystem_load_template_source',)
-
-MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
- 'util.middleware.ExceptionLoggingMiddleware',
- 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware',
- 'askbot.middleware.forum_mode.ForumModeMiddleware',
- 'askbot.middleware.cancel.CancelActionMiddleware',
- 'django.middleware.transaction.TransactionMiddleware',
- #'debug_toolbar.middleware.DebugToolbarMiddleware',
- 'askbot.middleware.view_log.ViewLogMiddleware',
- 'askbot.middleware.spaceless.SpacelessMiddleware',
- # 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
-)
-
-FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
-FILE_UPLOAD_HANDLERS = (
- 'django.core.files.uploadhandler.MemoryFileUploadHandler',
- 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
-)
-ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
-ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 #result in bytes
-# ASKBOT_FILE_UPLOAD_DIR = os.path.join(os.path.dirname(__file__), 'askbot', 'upfiles')
-
-PROJECT_ROOT = os.path.dirname(__file__)
-
-TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.core.context_processors.request',
- 'askbot.context.application_settings',
- #'django.core.context_processors.i18n',
- 'askbot.user_messages.context_processors.user_messages',#must be before auth
- 'django.core.context_processors.auth', #this is required for admin
- 'django.core.context_processors.csrf', #necessary for csrf protection
-)
-
-INSTALLED_APPS = INSTALLED_APPS + (
- 'django.contrib.sitemaps',
- 'django.contrib.admin',
- 'south',
- 'askbot.deps.livesettings',
- 'askbot',
- #'keyedcache', # TODO: Main askbot tree has this installed, but we get intermittent errors if we include it.
- 'robots',
- 'django_countries',
- 'djcelery',
- 'djkombu',
- 'followit',
-)
-
-# askbot livesettings
-LIVESETTINGS_OPTIONS = {
- 1: {
- 'SETTINGS' : {
- 'FORUM_DATA_RULES' : {
- 'MIN_TITLE_LENGTH' : 1,
- 'MIN_QUESTION_BODY_LENGTH' : 1,
- 'MIN_ANSWER_BODY_LENGTH' : 1,
-
- # 'ENABLE_VIDEO_EMBEDDING' : True,
- #
- # Enabling video requires forked version of markdown
- # pip uninstall markdown2
- # pip install -e git+git://github.com/andryuha/python-markdown2.git#egg=markdown2
- },
- 'MIN_REP' : {
- 'MIN_REP_TO_VOTE_UP' : 1,
- 'MIN_REP_TO_VOTE_DOWN' : 1,
- 'MIN_REP_TO_ANSWER_OWN_QUESTION' : 1,
- 'MIN_REP_TO_ACCEPT_OWN_ANSWER' : 1,
- 'MIN_REP_TO_FLAG_OFFENSIVE' : 1,
- 'MIN_REP_TO_LEAVE_COMMENTS' : 1,
- 'MIN_REP_TO_CLOSE_OWN_QUESTIONS' : 1,
- 'MIN_REP_TO_RETAG_OTHERS_QUESTIONS' : 1,
- 'MIN_REP_TO_REOPEN_OWN_QUESTIONS' : 1,
- 'MIN_REP_TO_EDIT_WIKI' : 1,
- 'MIN_REP_TO_CLOSE_OTHERS_QUESTIONS' : 100,
- 'MIN_REP_TO_UPLOAD_FILES' : 1,
- },
- 'SOCIAL_SHARING' : {
- 'ENABLE_SHARING_TWITTER' : False,
- 'ENABLE_SHARING_FACEBOOK' : False,
- 'ENABLE_SHARING_LINKEDIN' : False,
- 'ENABLE_SHARING_IDENTICA' : False,
- 'ENABLE_SHARING_GOOGLE' : False,
- },
- 'USER_SETTINGS' : {
- 'EDITABLE_SCREEN_NAME' : False,
- 'EDITABLE_EMAIL' : False,
- 'ALLOW_ADD_REMOVE_LOGIN_METHODS' : False,
- 'ENABLE_GRAVATAR' : False,
- }
- }
- },
-}
-
-
-CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
-ASKBOT_URL = 'discussion/'
-LOGIN_REDIRECT_URL = '/'
-LOGIN_URL = '/'
-
-# ASKBOT_UPLOADED_FILES_URL = '%s%s' % (ASKBOT_URL, 'upfiles/')
-ALLOW_UNICODE_SLUGS = False
-ASKBOT_USE_STACKEXCHANGE_URLS = False #mimic url scheme of stackexchange
-ASKBOT_CSS_DEVEL = True
-
-# Celery Settings
-BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport"
-CELERY_ALWAYS_EAGER = True
-
-djcelery.setup_loader()
-
diff --git a/settings_old_askbot.py b/settings_old_askbot.py
deleted file mode 100644
index b711440d65..0000000000
--- a/settings_old_askbot.py
+++ /dev/null
@@ -1,245 +0,0 @@
-if 'COURSEWARE_ENABLED' not in locals():
- COURSEWARE_ENABLED = True
-if 'ASKBOT_ENABLED' not in locals():
- ASKBOT_ENABLED = True
-if not COURSEWARE_ENABLED:
- ASKBOT_ENABLED = False
-
-# Defaults to be overridden
-EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
-SITE_NAME = "localhost:8000"
-
-DEFAULT_FROM_EMAIL = 'registration@mitx.mit.edu'
-DEFAULT_FEEDBACK_EMAIL = 'feedback@mitx.mit.edu'
-
-# For testing the login system
-GENERATE_RANDOM_USER_CREDENTIALS = False
-
-WIKI_REQUIRE_LOGIN_EDIT = True
-WIKI_REQUIRE_LOGIN_VIEW = True
-
-PERFSTATS = False
-
-HTTPS = 'on'
-
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-
-ADMINS = (
- ('Piotr Mitros', 'staff@csail.mit.edu'),
-)
-
-MANAGERS = ADMINS
-
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-TIME_ZONE = 'America/Chicago'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en'
-
-SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
-USE_I18N = True
-
-# If you set this to False, Django will not format dates, numbers and
-# calendars according to the current locale
-USE_L10N = True
-
-#MEDIA_ROOT = ''
-#MEDIA_URL = ''
-
-STATIC_URL = '/static/'
-
-# URL prefix for admin static files -- CSS, JavaScript and images.
-# Make sure to use a trailing slash.
-# Examples: "http://foo.com/static/admin/", "/static/admin/".
-ADMIN_MEDIA_PREFIX = '/static/admin/'
-
-
-# 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',
-# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
-)
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.Loader',
- 'django.template.loaders.app_directories.Loader',
-# 'django.template.loaders.eggs.Loader',
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'track.middleware.TrackMiddleware',
- 'mitxmako.middleware.MakoMiddleware',
- #'debug_toolbar.middleware.DebugToolbarMiddleware',
-)
-
-ROOT_URLCONF = 'mitx.urls'
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'courseware',
- 'auth',
- 'django.contrib.humanize',
- 'static_template_view',
- 'staticbook',
- 'simplewiki',
- 'track',
- 'circuit',
- 'perfstats',
- # Uncomment the next line to enable the admin:
- # 'django.contrib.admin',
- # Uncomment the next line to enable admin documentation:
- # 'django.contrib.admindocs',
-)
-
-# A sample logging configuration. The only tangible logging
-# performed by this configuration is to send an email to
-# the site admins on every HTTP 500 error.
-# See http://docs.djangoproject.com/en/dev/topics/logging for
-# more details on how to customize your logging configuration.
-LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'handlers': {
- 'mail_admins': {
- 'level': 'ERROR',
- 'class': 'django.utils.log.AdminEmailHandler'
- }
- },
- 'loggers': {
- 'django.request': {
- 'handlers': ['mail_admins'],
- 'level': 'ERROR',
- 'propagate': True,
- },
- }
-}
-
-#TRACK_DIR = None
-DEBUG_TRACK_LOG = False
-# Maximum length of a tracking string. We don't want e.g. a file upload in our log
-TRACK_MAX_EVENT = 1000
-# Maximum length of log file before starting a new one.
-MAXLOG = 500
-
-execfile("../settings.py")
-
-if PERFSTATS :
- MIDDLEWARE_CLASSES = ( 'perfstats.middleware.ProfileMiddleware',) + MIDDLEWARE_CLASSES
-
-if 'TRACK_DIR' not in locals():
- TRACK_DIR = BASE_DIR+'/track_dir/'
-if 'ASKBOT_EXTRA_SKINS_DIR' not in locals():
- ASKBOT_EXTRA_SKINS_DIR = BASE_DIR+'/askbot/skins'
-if 'ASKBOT_DIR' not in locals():
- ASKBOT_DIR = BASE_DIR
-if 'STATIC_ROOT' not in locals():
- STATIC_ROOT = BASE_DIR+'/staticroot/'
-if 'DATA_DIR' not in locals():
- DATA_DIR = BASE_DIR+'/data/'
-if 'TEXTBOOK_DIR' not in locals():
- TEXTBOOK_DIR = BASE_DIR+'/textbook/'
-
-if 'TEMPLATE_DIRS' not in locals():
- TEMPLATE_DIRS = (
- BASE_DIR+'/templates/',
- DATA_DIR+'/templates',
- TEXTBOOK_DIR,
- )
-
-if 'STATICFILES_DIRS' not in locals():
- STATICFILES_DIRS = (
- BASE_DIR+'/3rdParty/static',
- BASE_DIR+'/static'
- )
-
-if ASKBOT_ENABLED:
- import sys
- sys.path.append(ASKBOT_DIR)
- import os
- import askbot
- import site
- site.addsitedir(os.path.join(os.path.dirname(askbot.__file__), 'deps'))
- TEMPLATE_LOADERS = TEMPLATE_LOADERS + ('askbot.skins.loaders.filesystem_load_template_source',)
-
- MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
- 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware',
- 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
- 'askbot.middleware.cancel.CancelActionMiddleware',
- 'django.middleware.transaction.TransactionMiddleware',
- 'askbot.middleware.view_log.ViewLogMiddleware',
- 'askbot.middleware.spaceless.SpacelessMiddleware',
- 'askbot.middleware.forum_mode.ForumModeMiddleware',
- )
-
- FILE_UPLOAD_TEMP_DIR = os.path.join(
- os.path.dirname(__file__),
- 'tmp'
- ).replace('\\','/')
- FILE_UPLOAD_HANDLERS = (
- 'django.core.files.uploadhandler.MemoryFileUploadHandler',
- 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
- )
- ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
- ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 #result in bytes
- ASKBOT_FILE_UPLOAD_DIR = os.path.join(os.path.dirname(__file__), 'askbot', 'upfiles')
- DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
-
- PROJECT_ROOT = os.path.dirname(__file__)
-
- TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.core.context_processors.request',
- 'askbot.context.application_settings',
- #'django.core.context_processors.i18n',
- 'askbot.user_messages.context_processors.user_messages',#must be before auth
- 'django.core.context_processors.auth', #this is required for admin
- 'django.core.context_processors.csrf', #necessary for csrf protection
- )
-
- INSTALLED_APPS = INSTALLED_APPS + (
- 'django.contrib.sitemaps',
- 'django.contrib.admin',
- 'south',
- 'askbot.deps.livesettings',
- 'askbot',
- #'keyedcache', # TODO: Main askbot tree has this installed, but we get intermittent errors if we include it.
- 'robots',
- 'django_countries',
- 'djcelery',
- 'djkombu',
- 'followit',
- )
-
- CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
- ASKBOT_URL = 'discussion/'
- LOGIN_REDIRECT_URL = '/'
- LOGIN_URL = '/'
-
- ASKBOT_UPLOADED_FILES_URL = '%s%s' % (ASKBOT_URL, 'upfiles/')
- ALLOW_UNICODE_SLUGS = False
- ASKBOT_USE_STACKEXCHANGE_URLS = False #mimic url scheme of stackexchange
- ASKBOT_CSS_DEVEL = True
-
- #Celery Settings
- BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport"
- CELERY_ALWAYS_EAGER = True
-
- import djcelery
- djcelery.setup_loader()
diff --git a/static_template_view/views.py b/static_template_view/views.py
index a66a3cd8c5..d3a12a9fdb 100644
--- a/static_template_view/views.py
+++ b/static_template_view/views.py
@@ -9,20 +9,23 @@ from django.core.context_processors import csrf
from django.conf import settings
#valid_templates=['index.html', 'staff.html', 'info.html', 'credits.html']
-valid_templates=['mitx_global.html',
- 'index.html',
+valid_templates=['index.html',
'tos.html',
'privacy.html',
'honor.html',
'copyright.html',
- '404.html']
-
-print "!!",settings.__dict__
+ '404.html',
+ 'mitx_help.html']
if settings.STATIC_GRAB:
valid_templates = valid_templates+['server-down.html',
'server-error.html'
- 'server-overloaded.html']
+ 'server-overloaded.html',
+ 'mitx_global.html',
+ 'mitx-overview.html',
+ '6002x-faq.html',
+ '6002x-press-release.html'
+ ]
def index(request, template):
csrf_token = csrf(request)['csrf_token']
diff --git a/student/migrations/0003_auto__add_usertestgroup.py b/student/migrations/0003_auto__add_usertestgroup.py
new file mode 100644
index 0000000000..4765d63578
--- /dev/null
+++ b/student/migrations/0003_auto__add_usertestgroup.py
@@ -0,0 +1,124 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'UserTestGroup'
+ db.create_table('student_usertestgroup', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=32, db_index=True)),
+ ('description', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ))
+ db.send_create_signal('student', ['UserTestGroup'])
+
+ # Adding M2M table for field users on 'UserTestGroup'
+ db.create_table('student_usertestgroup_users', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('usertestgroup', models.ForeignKey(orm['student.usertestgroup'], null=False)),
+ ('user', models.ForeignKey(orm['auth.user'], null=False))
+ ))
+ db.create_unique('student_usertestgroup_users', ['usertestgroup_id', 'user_id'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'UserTestGroup'
+ db.delete_table('student_usertestgroup')
+
+ # Removing M2M table for field users on 'UserTestGroup'
+ db.delete_table('student_usertestgroup_users')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
+ 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+ 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
+ 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'student.registration': {
+ 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"},
+ 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
+ },
+ 'student.userprofile': {
+ 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
+ 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
+ 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
+ 'meta': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
+ },
+ 'student.usertestgroup': {
+ 'Meta': {'object_name': 'UserTestGroup'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'})
+ }
+ }
+
+ complete_apps = ['student']
diff --git a/student/migrations/0004_add_email_index.py b/student/migrations/0004_add_email_index.py
new file mode 100644
index 0000000000..c95e36ae96
--- /dev/null
+++ b/student/migrations/0004_add_email_index.py
@@ -0,0 +1,106 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ db.execute("create unique index email on auth_user (email)")
+ pass
+
+
+ def backwards(self, orm):
+ db.execute("drop index email on auth_user")
+ pass
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
+ 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+ 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
+ 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'student.registration': {
+ 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"},
+ 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
+ },
+ 'student.userprofile': {
+ 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
+ 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
+ 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
+ 'meta': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
+ },
+ 'student.usertestgroup': {
+ 'Meta': {'object_name': 'UserTestGroup'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'})
+ }
+ }
+
+ complete_apps = ['student']
diff --git a/student/models.py b/student/models.py
index 6c614966f9..b9554369bc 100644
--- a/student/models.py
+++ b/student/models.py
@@ -27,6 +27,10 @@ class UserProfile(models.Model):
meta = models.CharField(blank=True, max_length=255) # JSON dictionary for future expansion
courseware = models.CharField(blank=True, max_length=255, default='course.xml')
+class UserTestGroup(models.Model):
+ users = models.ManyToManyField(User, db_index=True)
+ name = models.CharField(blank=False, max_length=32, db_index=True)
+ description = models.TextField(blank=True)
class Registration(models.Model):
''' Allows us to wait for e-mail before user is registered. A
diff --git a/student/views.py b/student/views.py
index 397f32dc39..da82ee3ad1 100644
--- a/student/views.py
+++ b/student/views.py
@@ -28,27 +28,18 @@ def csrf_token(context):
@ensure_csrf_cookie
def index(request):
if settings.COURSEWARE_ENABLED and request.user.is_authenticated():
- return redirect('/courseware')
+ return redirect('/info')
else:
csrf_token = csrf(request)['csrf_token']
# TODO: Clean up how 'error' is done.
- return render_to_response('index.html', {'error' : '',
- 'csrf': csrf_token })
+ return render_to_response('index.html', {'csrf': csrf_token })
-# def courseinfo(request):
-# if request.user.is_authenticated():
-# return redirect('/courseware')
-# else:
-# csrf_token = csrf(request)['csrf_token']
-# # TODO: Clean up how 'error' is done.
-# return render_to_response('courseinfo.html', {'error' : '',
-# 'csrf': csrf_token })
-
# Need different levels of logging
@ensure_csrf_cookie
def login_user(request, error=""):
if 'email' not in request.POST or 'password' not in request.POST:
- return render_to_response('login.html', {'error':error.replace('+',' ')})
+ return HttpResponse(json.dumps({'success':False,
+ 'error': 'Invalid login'})) # TODO: User error message
email = request.POST['email']
password = request.POST['password']
@@ -136,9 +127,15 @@ def create_account(request, post_override=None):
# TODO: Confirm e-mail is not from a generic domain (mailinator, etc.)? Not sure if
# this is a good idea
# TODO: Check password is sane
- for a in ['username', 'email', 'password', 'terms_of_service', 'honor_code']:
+ for a in ['username', 'email', 'name', 'password', 'terms_of_service', 'honor_code']:
if len(post_vars[a])<2:
- js['value']="{field} is required.".format(field=a)
+ error_str = {'username' : 'Username of length 2 or greater',
+ 'email' : 'Properly formatted e-mail',
+ 'name' : 'Your legal name ',
+ 'password': 'Valid password ',
+ 'terms_of_service': 'Accepting Terms of Service',
+ 'honor_code': 'Agreeing to the Honor Code'}
+ js['value']="{field} is required.".format(field=error_str[a])
return HttpResponse(json.dumps(js))
try:
diff --git a/track/views.py b/track/views.py
index a06baa6212..6b7256c756 100644
--- a/track/views.py
+++ b/track/views.py
@@ -1,6 +1,7 @@
import json
import logging
import os
+import datetime
# Create your views here.
from django.http import HttpResponse
@@ -39,6 +40,7 @@ def user_track(request):
"event" : request.GET['event'],
"agent" : agent,
"page" : request.GET['page'],
+ "time": datetime.datetime.utcnow().isoformat(),
}
log_event(event)
return HttpResponse('success')
@@ -62,5 +64,6 @@ def server_track(request, event_type, event, page=None):
"event" : event,
"agent" : agent,
"page" : page,
+ "time": datetime.datetime.utcnow().isoformat(),
}
log_event(event)
diff --git a/urls.py b/urls.py
index e876f4098f..6e882e1580 100644
--- a/urls.py
+++ b/urls.py
@@ -40,6 +40,7 @@ if settings.COURSEWARE_ENABLED:
url(r'^courseware/(?P[^/]*)/(?P[^/]*)/(?P[^/]*)/$', 'courseware.views.index', name="courseware_section"),
url(r'^courseware/(?P[^/]*)/(?P[^/]*)/$', 'courseware.views.index', name="courseware_chapter"),
url(r'^courseware/(?P[^/]*)/$', 'courseware.views.index', name="courseware_course"),
+ url(r'^section/(?P[^/]*)/$', 'courseware.views.render_section'),
url(r'^modx/(?P[^/]*)/(?P[^/]*)/(?P[^/]*)$', 'courseware.views.modx_dispatch'), #reset_problem'),
url(r'^profile$', 'courseware.views.profile'),
url(r'^change_setting$', 'student.views.change_setting'),
@@ -63,3 +64,4 @@ if settings.ASKBOT_ENABLED:
)
urlpatterns = patterns(*urlpatterns)
+
diff --git a/util/middleware.py b/util/middleware.py
index fe73e67c7b..342fff1790 100644
--- a/util/middleware.py
+++ b/util/middleware.py
@@ -1,7 +1,7 @@
import logging
from django.conf import settings
-from django.http import HttpResponse
+from django.http import HttpResponseServerError
log = logging.getLogger("mitx")
@@ -12,4 +12,4 @@ class ExceptionLoggingMiddleware(object):
if not settings.TEMPLATE_DEBUG:
def process_exception(self, request, exception):
log.exception(exception)
- return HttpResponse("Server Error - Please try again later.")
+ return HttpResponseServerError("Server Error - Please try again later.")
diff --git a/util/views.py b/util/views.py
index 83d525708a..a479277444 100644
--- a/util/views.py
+++ b/util/views.py
@@ -51,4 +51,7 @@ def send_feedback(request):
def info(request):
''' Info page (link from main header) '''
+ if not request.user.is_authenticated():
+ return redirect('/')
+
return render_to_response("info.html", {})