common.py 17.9 KB
Newer Older
1 2 3 4
"""
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
5
MITX_FEATURES[...]. Modules that extend this one can change the feature
6 7 8
configuration in an environment specific config file and re-calculate those
values.

9
We should make a method that calls all these config methods so that you just
10
make one call at the end of your site-specific dev file to reset all the
11 12
dependent variables (like INSTALLED_APPS) for you.

13
Longer TODO:
14
1. Right now our treatment of static content in general and in particular
15 16 17
   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.
18
3. We need to handle configuration for multiple courses. This could be as
19 20
   multiple sites, but we do need a way to map their data assets.
"""
21
import sys
22
import os
Piotr Mitros committed
23
import tempfile
24
import glob2
25
import errno
26
import hashlib
27
from collections import defaultdict
28 29

import djcelery
30 31
from path import path

32
from .askbotsettings import * # this is where LIVESETTINGS_OPTIONS comes from
33

34 35 36 37 38 39 40 41
################################### FEATURES ###################################
COURSEWARE_ENABLED = True
ASKBOT_ENABLED = True
GENERATE_RANDOM_USER_CREDENTIALS = False
PERFSTATS = False

# Features
MITX_FEATURES = {
42 43
    'SAMPLE' : False,
    'USE_DJANGO_PIPELINE' : True,
44
    'DISPLAY_HISTOGRAMS_TO_STAFF' : True,
45
    'REROUTE_ACTIVATION_EMAIL' : False,		# nonempty string = address for all activation emails
46
    'DEBUG_LEVEL' : 0,				# 0 = lowest level, least verbose, 255 = max level, most verbose
47 48 49

    ## DO NOT SET TO True IN THIS FILE
    ## Doing so will cause all courses to be released on production
50
    'DISABLE_START_DATES': False,  # When True, all courses will be active, regardless of start date
51 52
}

53 54 55 56 57 58
# Used for A/B testing
DEFAULT_GROUPS = []

# If this is true, random scores will be generated for the purpose of debugging the profile graphs
GENERATE_PROFILE_SCORES = False

59
############################# SET PATH INFORMATION #############################
60 61 62 63
PROJECT_ROOT = path(__file__).abspath().dirname().dirname()  # /mitx/lms
REPO_ROOT = PROJECT_ROOT.dirname()
COMMON_ROOT = REPO_ROOT / "common"
ENV_ROOT = REPO_ROOT.dirname()  # virtualenv dir /mitx is in
64
ASKBOT_ROOT = REPO_ROOT / "askbot"
65 66 67 68 69
COURSES_ROOT = ENV_ROOT / "data"

# FIXME: To support multiple courses, we should walk the courses dir at startup
DATA_DIR = COURSES_ROOT

70
sys.path.append(REPO_ROOT)
71 72 73 74
sys.path.append(ASKBOT_ROOT)
sys.path.append(ASKBOT_ROOT / "askbot" / "deps")
sys.path.append(PROJECT_ROOT / 'djangoapps')
sys.path.append(PROJECT_ROOT / 'lib')
75
sys.path.append(COMMON_ROOT / 'djangoapps')
76
sys.path.append(COMMON_ROOT / 'lib')
77 78

################################## MITXWEB #####################################
79 80
# This is where we stick our compiled template files. Most of the app uses Mako
# templates
81
MAKO_MODULE_DIR = tempfile.mkdtemp('mako')
Piotr Mitros committed
82
MAKO_TEMPLATES = {}
83
MAKO_TEMPLATES['main'] = [PROJECT_ROOT / 'templates',
84
                          COMMON_ROOT / 'templates',
85
                          COMMON_ROOT / 'lib' / 'capa' / 'capa' / 'templates',
86
                          COMMON_ROOT / 'djangoapps' / 'pipeline_mako' / 'templates']
Piotr Mitros committed
87

88
# This is where Django Template lookup is defined. There are a few of these
89 90 91 92 93 94 95
# still left lying around.
TEMPLATE_DIRS = (
    PROJECT_ROOT / "templates",
)

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.core.context_processors.request',
96
    'django.core.context_processors.static',
97
    'askbot.context.application_settings',
98
    'django.contrib.messages.context_processors.messages',
99 100 101 102 103 104 105
    #'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
)


106 107
# FIXME:
# We should have separate S3 staged URLs in case we need to make changes to
108
# these assets and test them.
109
LIB_URL = '/static/js/'
110 111 112 113

# Dev machines shouldn't need the book
# BOOK_URL = '/static/book/'
BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/' # For AWS deploys
114
# RSS_URL = r'lms/templates/feed.rss'
115
# PRESS_URL = r''
116
RSS_TIMEOUT = 600
117

118 119 120 121
# Configuration option for when we want to grab server error pages
STATIC_GRAB = False
DEV_CONTENT = True

122
# FIXME: Should we be doing this truncation?
123
TRACK_MAX_EVENT = 10000
124
DEBUG_TRACK_LOG = False
125

126 127 128 129 130 131
MITX_ROOT_URL = ''

COURSE_NAME = "6.002_Spring_2012"
COURSE_NUMBER = "6.002x"
COURSE_TITLE = "Circuits and Electronics"

132
### Dark code. Should be enabled in local settings for devel.
133 134

ENABLE_MULTICOURSE = False     # set to False to disable multicourse display (see lib.util.views.mitxhome)
135
QUICKEDIT = False
136

137 138
WIKI_ENABLED = False

139 140
###

141 142
COURSE_DEFAULT = '6.002x_Fall_2012'
COURSE_SETTINGS =  {'6.002x_Fall_2012': {'number' : '6.002x',
143 144
                                          'title'  :  'Circuits and Electronics',
                                          'xmlpath': '6002x/',
145
                                          'location': 'i4x://edx/6002xs12/course/6.002x_Fall_2012',
146 147 148 149
                                          }
                    }


150
############################### XModule Store ##################################
151
MODULESTORE = {
152
    'default': {
153
        'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
154 155 156
        'OPTIONS': {
            'data_dir': DATA_DIR,
            'default_class': 'xmodule.hidden_module.HiddenDescriptor',
157
            'eager': True,
158 159 160 161 162
        }
    }
}


163 164 165 166
############################### DJANGO BUILT-INS ###############################
# Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here
DEBUG = False
TEMPLATE_DEBUG = False
167

168 169
# Site info
SITE_ID = 1
170
SITE_NAME = "edx.org"
171
HTTPS = 'on'
172
ROOT_URLCONF = 'lms.urls'
173
IGNORABLE_404_ENDS = ('favicon.ico')
174

175
# Email
176
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
177 178
DEFAULT_FROM_EMAIL = 'registration@edx.org'
DEFAULT_FEEDBACK_EMAIL = 'feedback@edx.org'
179
ADMINS = (
180
    ('edX Admins', 'admin@edx.org'),
181 182 183
)
MANAGERS = ADMINS

184
# Static content
185 186
STATIC_URL = '/static/'
ADMIN_MEDIA_PREFIX = '/static/admin/'
187
STATIC_ROOT = ENV_ROOT / "staticfiles"
188

189
STATICFILES_DIRS = [
190
    COMMON_ROOT / "static",
191
    PROJECT_ROOT / "static",
192
    ASKBOT_ROOT / "askbot" / "skins",
193

194
]
195 196 197 198 199 200
if os.path.isdir(DATA_DIR):
    STATICFILES_DIRS += [
        # TODO (cpennington): When courses are stored in a database, this
        # should no longer be added to STATICFILES
        (course_dir, DATA_DIR / course_dir)
        for course_dir in os.listdir(DATA_DIR)
201
        if os.path.isdir(DATA_DIR / course_dir)
202
    ]
203

204 205 206 207 208
# 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
209

210 211 212
# Messages
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'

213
#################################### AWS #######################################
214
# S3BotoStorage insists on a timeout for uploaded assets. We should make it
215
# permanent instead, but rather than trying to figure out exactly where that
216
# setting is, I'm just bumping the expiration time to something absurd (100
217 218 219
# 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
220

221
################################### ASKBOT #####################################
222 223 224 225 226 227 228 229
LIVESETTINGS_OPTIONS['MITX_ROOT_URL'] = MITX_ROOT_URL
skin_settings = LIVESETTINGS_OPTIONS[1]['SETTINGS']['GENERAL_SKIN_SETTINGS']
skin_settings['SITE_FAVICON'] = unicode(MITX_ROOT_URL) + skin_settings['SITE_FAVICON']
skin_settings['SITE_LOGO_URL'] = unicode(MITX_ROOT_URL) +  skin_settings['SITE_LOGO_URL']
skin_settings['LOCAL_LOGIN_ICON'] = unicode(MITX_ROOT_URL) + skin_settings['LOCAL_LOGIN_ICON']
LIVESETTINGS_OPTIONS[1]['SETTINGS']['LOGIN_PROVIDERS']['WORDPRESS_SITE_ICON'] = unicode(MITX_ROOT_URL) + LIVESETTINGS_OPTIONS[1]['SETTINGS']['LOGIN_PROVIDERS']['WORDPRESS_SITE_ICON']
LIVESETTINGS_OPTIONS[1]['SETTINGS']['LICENSE_SETTINGS']['LICENSE_LOGO_URL'] = unicode(MITX_ROOT_URL) + LIVESETTINGS_OPTIONS[1]['SETTINGS']['LICENSE_SETTINGS']['LICENSE_LOGO_URL']

Matthew Mongeau committed
230 231
# ASKBOT_EXTRA_SKINS_DIR = ASKBOT_ROOT / "askbot" / "skins"
ASKBOT_EXTRA_SKINS_DIR =  PROJECT_ROOT / "askbot" / "skins"
232
ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
233
ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 # result in bytes
234

235
CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
236
CACHE_PREFIX = SITE_ID
237
ASKBOT_URL = 'discussion/'
238 239
LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/'
LOGIN_URL = MITX_ROOT_URL + '/'
240

241 242 243
ALLOW_UNICODE_SLUGS = False
ASKBOT_USE_STACKEXCHANGE_URLS = False # mimic url scheme of stackexchange
ASKBOT_CSS_DEVEL = True
244 245 246 247 248

# Celery Settings
BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport"
CELERY_ALWAYS_EAGER = True
djcelery.setup_loader()
Piotr Mitros committed
249

250
################################# SIMPLEWIKI ###################################
251 252
SIMPLE_WIKI_REQUIRE_LOGIN_EDIT = True
SIMPLE_WIKI_REQUIRE_LOGIN_VIEW = False
253

254
################################# Jasmine ###################################
255
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
256

257 258 259 260
################################# Middleware ###################################
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
261 262
    'staticfiles.finders.FileSystemFinder',
    'staticfiles.finders.AppDirectoriesFinder',
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
)

# 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',
278 279

    # Instead of AuthenticationMiddleware, we use a cached backed version
280 281
    #'django.contrib.auth.middleware.AuthenticationMiddleware',
    'cache_toolbox.middleware.CacheBackedAuthenticationMiddleware',
282

283 284 285
    'django.contrib.messages.middleware.MessageMiddleware',
    'track.middleware.TrackMiddleware',
    'mitxmako.middleware.MakoMiddleware',
286

287 288 289 290 291 292 293
    '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',
294
    # 'debug_toolbar.middleware.DebugToolbarMiddleware',
295 296
)

297 298 299 300 301 302
############################### Pipeline #######################################

STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'

PIPELINE_CSS = {
    'application': {
303
        'source_filenames': ['sass/application.scss'],
304 305
        'output_filename': 'css/application.css',
    },
Kyle Fiedler committed
306
    'course': {
307
      'source_filenames': ['sass/course.scss', 'js/vendor/CodeMirror/codemirror.css', 'css/vendor/jquery.treeview.css'],
Kyle Fiedler committed
308 309
      'output_filename': 'css/course.css',
      },
310 311 312 313
    'ie-fixes': {
        'source_filenames': ['sass/ie.scss'],
        'output_filename': 'css/ie.css',
    },
314 315
}

316
PIPELINE_ALWAYS_RECOMPILE = ['sass/application.scss', 'sass/ie.scss', 'sass/course.scss']
317

318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
courseware_only_js = [
    PROJECT_ROOT / 'static/coffee/src/' + pth + '.coffee'
    for pth
    in ['courseware', 'histogram', 'navigation', 'time', ]
]
courseware_only_js += [
    pth for pth
    in glob2.glob(PROJECT_ROOT / 'static/coffee/src/modules/**/*.coffee')
]

main_vendor_js = [
  'js/vendor/jquery.min.js',
  'js/vendor/jquery-ui.min.js',
  'js/vendor/swfobject/swfobject.js',
  'js/vendor/jquery.cookie.js',
  'js/vendor/jquery.qtip.min.js',
]
335

336 337 338 339 340
# Load javascript from all of the available xmodules, and
# prep it for use in pipeline js
from xmodule.x_module import XModuleDescriptor
from xmodule.hidden_module import HiddenDescriptor
js_file_dir = PROJECT_ROOT / "static" / "coffee" / "module"
341 342 343 344 345 346 347 348 349 350 351 352 353 354
css_file_dir = PROJECT_ROOT / "static" / "sass" / "module"
module_styles_path = css_file_dir / "_module-styles.scss"

for dir_ in (js_file_dir, css_file_dir):
    try:
        os.makedirs(dir_)
    except OSError as exc:
        if exc.errno == errno.EEXIST:
            pass
        else:
            raise

js_fragments = set()
css_fragments = defaultdict(set)
355
for descriptor in XModuleDescriptor.load_classes() + [HiddenDescriptor]:
356
    module_js = descriptor.module_class.get_javascript()
357
    for filetype in ('coffee', 'js'):
358
        for idx, fragment in enumerate(module_js.get(filetype, [])):
359 360 361 362 363 364
            js_fragments.add((idx, filetype, fragment))

    module_css = descriptor.module_class.get_css()
    for filetype in ('sass', 'scss', 'css'):
        for idx, fragment in enumerate(module_css.get(filetype, [])):
            css_fragments[idx, filetype, fragment].add(descriptor.module_class.__name__)
365 366

module_js_sources = []
367 368
for idx, filetype, fragment in sorted(js_fragments):
    path = js_file_dir / "{idx}-{hash}.{type}".format(
369
        idx=idx,
370
        hash=hashlib.md5(fragment).hexdigest(),
371
        type=filetype)
372 373 374
    with open(path, 'w') as js_file:
        js_file.write(fragment)
    module_js_sources.append(path.replace(PROJECT_ROOT / "static/", ""))
375

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
css_imports = defaultdict(set)
for (idx, filetype, fragment), classes in sorted(css_fragments.items()):
    fragment_name = "{idx}-{hash}.{type}".format(
        idx=idx,
        hash=hashlib.md5(fragment).hexdigest(),
        type=filetype)
    # Prepend _ so that sass just includes the files into a single file
    with open(css_file_dir / '_' + fragment_name, 'w') as js_file:
        js_file.write(fragment)

    for class_ in classes:
        css_imports[class_].add(fragment_name)

with open(module_styles_path, 'w') as module_styles:
    for class_, fragment_names in css_imports.items():
        imports = "\n".join('@import "{0}";'.format(name) for name in fragment_names)
        module_styles.write(""".xmodule_{class_} {{ {imports} }}""".format(
            class_=class_, imports=imports
        ))
395

396 397
PIPELINE_JS = {
    'application': {
398
        # Application will contain all paths not in courseware_only_js
399
        'source_filenames': [
400 401 402 403
            pth.replace(COMMON_ROOT / 'static/', '')
            for pth
            in glob2.glob(COMMON_ROOT / 'static/coffee/src/**/*.coffee')
        ] + [
404 405 406
            pth.replace(PROJECT_ROOT / 'static/', '')
            for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee')\
            if pth not in courseware_only_js
407 408 409 410 411 412
        ] + [
            'js/form.ext.js',
            'js/my_courses_dropdown.js',
            'js/toggle_login_modal.js',
            'js/sticky_filter.js',
        ],
413
        'output_filename': 'js/application.js'
414
    },
415
    'courseware': {
416 417 418
        'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in courseware_only_js],
        'output_filename': 'js/courseware.js'
    },
419 420 421 422
    'main_vendor': {
        'source_filenames': main_vendor_js,
        'output_filename': 'js/main_vendor.js',
    },
423 424 425 426
    'module-js': {
        'source_filenames': module_js_sources,
        'output_filename': 'js/modules.js',
    },
427
    'spec': {
428
        'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/spec/**/*.coffee')],
429
        'output_filename': 'js/spec.js'
430 431 432
    }
}

433 434 435
# Compile all coffee files in course data directories if they are out of date.
# TODO: Remove this once we move data into Mongo. This is only temporary while
# course data directories are still in use.
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
if os.path.isdir(DATA_DIR):
    for course_dir in os.listdir(DATA_DIR):
        js_dir = DATA_DIR / course_dir / "js"
        if not os.path.isdir(js_dir):
            continue
        for filename in os.listdir(js_dir):
            if filename.endswith('coffee'):
                new_filename = os.path.splitext(filename)[0] + ".js"
                if os.path.exists(js_dir / new_filename):
                    coffee_timestamp = os.stat(js_dir / filename).st_mtime
                    js_timestamp     = os.stat(js_dir / new_filename).st_mtime
                    if coffee_timestamp <= js_timestamp:
                        continue
                os.system("coffee -c %s" % (js_dir / filename))

451
PIPELINE_COMPILERS = [
452 453
    'pipeline.compilers.sass.SASSCompiler',
    'pipeline.compilers.coffee.CoffeeScriptCompiler',
454 455
]

456
PIPELINE_SASS_ARGUMENTS = '-t compressed -r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
457 458

PIPELINE_CSS_COMPRESSOR = None
459
PIPELINE_JS_COMPRESSOR = None
460

461
STATICFILES_IGNORE_PATTERNS = (
462 463
    "sass/*",
    "coffee/*",
464
    "*.py",
465
    "*.pyc"
466 467
)

468 469 470
PIPELINE_YUI_BINARY = 'yui-compressor'
PIPELINE_SASS_BINARY = 'sass'
PIPELINE_COFFEE_SCRIPT_BINARY = 'coffee'
471

472 473 474
# Setting that will only affect the MITx version of django-pipeline until our changes are merged upstream
PIPELINE_COMPILE_INPLACE = True

475
################################### APPS #######################################
476 477 478 479 480 481 482 483 484 485
INSTALLED_APPS = (
    # Standard ones that are always installed...
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.humanize',
    'django.contrib.messages',
    'django.contrib.sessions',
    'django.contrib.sites',
    'south',

486 487 488 489
    # For asset pipelining
    'pipeline',
    'staticfiles',

490 491 492 493 494 495 496 497 498 499
    # Our courseware
    'circuit',
    'courseware',
    'perfstats',
    'student',
    'static_template_view',
    'staticbook',
    'simplewiki',
    'track',
    'util',
500
    'certificates',
501

502 503 504
    # For testing
    'django_jasmine',

505
    # For Askbot
506 507 508 509 510 511 512 513 514
    'django.contrib.sitemaps',
    'django.contrib.admin',
    'django_countries',
    'djcelery',
    'djkombu',
    'askbot',
    'askbot.deps.livesettings',
    'followit',
    'keyedcache',
Matthew Mongeau committed
515
    'robots'
516
)