Completed
Push — master ( 0542e1...be5067 )
by Peter
12s
created

Config   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 63
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 20
c 2
b 0
f 0
dl 0
loc 63
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __init__() 0 16 3
A has_option() 0 2 1
A get_bool() 0 3 1
F get() 0 30 15
1
import os
2
from configparser import SafeConfigParser
3
4
from django.core.exceptions import ImproperlyConfigured
5
6
script_dir = os.path.dirname(__file__)
7
VERSION = open(os.path.join(script_dir, 'VERSION')).read()
8
9
NOT_CONFIGURED_VALUE = '***not configured***'
10
11
12
class Config():
13
    '''
14
    Custom implementation of INI-file reading with ENV variable
15
    pverride.
16
    '''
17
    config_file = None
18
    config = None
19
    is_production = None
20
21
    def __init__(self, config_files):
22
        '''
23
        Creates a new config object.
24
25
        Parameters:
26
        config_files: Dictionary with file_name: is_production setting
27
        '''
28
        for config_file, is_production in config_files:
29
            if os.path.isfile(config_file):
30
                self.config_file = config_file
31
                self.is_production = is_production
32
                self.config = SafeConfigParser()
33
                self.config.read([self.config_file], encoding='utf-8')
34
                return
35
36
        raise IOError("No configuration file found.")
37
38
    def has_option(self, name, category):
39
        return self.config.has_option(name, category)
40
41
    def get_bool(self, name, category):
42
        text = self.get(name, category)
43
        return text.lower() in ['true', 't', 'yes', 'active', 'enabled']
44
45
    def get(self, name, category, mandatory=False, expect_leading_slash=None, expect_trailing_slash=None):
46
        '''
47
        Get the value for the config variable.
48
        '''
49
50
        # Check environment variables for overrides, otherwise use INI file
51
        env_name = 'OPENSUBMIT_%s_%s'.format(category.upper(), name.upper())
52
        text = os.getenv(env_name)
53
        if text is None:
54
            text = self.config.get(name, category)
55
            logtext = "Setting '[%s] %s' in %s has the value '%s'" % (category, name, self.config_file, text)
56
        else:
57
            logtext = "Environment variable %s has the value '%s'" % (env_name, text)
58
59
        if mandatory and text == NOT_CONFIGURED_VALUE:
60
            raise ImproperlyConfigured(logtext + ', but must be set.')
61
62
        if len(text) == 0:
63
            if expect_leading_slash or expect_trailing_slash:
64
                raise ImproperlyConfigured(logtext + ", but should not be empty.")
65
        else:
66
            if not text[0] == '/' and expect_leading_slash is True:
67
                raise ImproperlyConfigured(logtext + ", but should have a leading slash.")
68
            if not text[-1] == '/' and expect_trailing_slash is True:
69
                raise ImproperlyConfigured(logtext + ", but should have a trailing slash.")
70
            if text[0] == '/' and expect_leading_slash is False:
71
                raise ImproperlyConfigured(logtext + ", but shouldn't have a leading slash.")
72
            if text[-1] == '/' and expect_trailing_slash is False:
73
                raise ImproperlyConfigured(logtext + ", but shouldn't have a trailing slash.")
74
        return text
75
76
77
# Precedence rules are as follows:
78
# - Developer configuration overrides production configuration on developer machine.
79
# - Linux production system are more likely to happen than Windows developer machines.
80
config = Config((
81
    ('/etc/opensubmit/settings.ini', True),  # Linux production system
82
    (os.path.dirname(__file__) + '/settings_dev.ini', False),  # Linux / Mac development system
83
    (os.path.expandvars('$APPDATA') + 'opensubmit/settings.ini', False),  # Windows development system
84
    ))
85
86
################################################################################################################
87
################################################################################################################
88
################################################################################################################
89
90
# Global settings
91
DATABASES = {
92
    'default': {
93
        'ENGINE': 'django.db.backends.' + config.get('database', 'DATABASE_ENGINE'),
94
        'NAME': config.get('database', 'DATABASE_NAME', True),
95
        'USER': config.get('database', 'DATABASE_USER'),
96
        'PASSWORD': config.get('database', 'DATABASE_PASSWORD'),
97
        'HOST': config.get('database', 'DATABASE_HOST'),
98
        'PORT': config.get('database', 'DATABASE_PORT'),
99
    }
100
}
101
102
# We have the is_production indicator from above, which could also determine this value.
103
# But sometimes, you need Django stack traces in your production system for debugging.
104
DEBUG = config.get_bool('general', 'DEBUG')
105
106
# Determine MAIN_URL / FORCE_SCRIPT option
107
HOST = config.get('server', 'HOST')
108
HOST_DIR = config.get('server', 'HOST_DIR')
109
if len(HOST_DIR) > 0:
110
    MAIN_URL = HOST + '/' + HOST_DIR
111
    FORCE_SCRIPT_NAME = '/' + HOST_DIR
112
else:
113
    MAIN_URL = HOST
114
    FORCE_SCRIPT_NAME = ''
115
116
# Determine some settings based on the MAIN_URL
117
LOGIN_URL = MAIN_URL
118
LOGIN_ERROR_URL = MAIN_URL
119
LOGIN_REDIRECT_URL = MAIN_URL + '/dashboard/'
120
121
# Local file system storage for uploads.
122
# Please note that MEDIA_URL is intentionally not set, since all media
123
# downloads have to use our download API URL for checking permissions.
124
MEDIA_ROOT = config.get('server', 'MEDIA_ROOT', True, True, True)
125
126
# Root of the installation
127
# This is normally detected automatically, so the settings.ini template does
128
# not contain the value. For the test suite, however, we need the override option.
129
if config.has_option('general', 'SCRIPT_ROOT'):
130
    SCRIPT_ROOT = config.get('general', 'SCRIPT_ROOT', True, True, False)
131
else:
132
    SCRIPT_ROOT = os.path.dirname(os.path.abspath(__file__))
133
134
if config.is_production:
135
    # Root folder for static files
136
    STATIC_ROOT = SCRIPT_ROOT + '/static-production/'
137
    STATICFILES_DIRS = (SCRIPT_ROOT + '/static/', )
138
    # Absolute URL for static files, directly served by Apache on production systems
139
    STATIC_URL = MAIN_URL + '/static/'
140
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
141
    ALLOWED_HOSTS = [MAIN_URL.split('/')[2]]
142
    if ':' in ALLOWED_HOSTS[0]:
143
        ALLOWED_HOSTS = [ALLOWED_HOSTS[0].split(':')[0]]
144
    SERVER_EMAIL = config.get('admin', 'ADMIN_EMAIL')
145
else:
146
    # Root folder for static files
147
    STATIC_ROOT = SCRIPT_ROOT + '/static/'
148
    # Relative URL for static files
149
    STATIC_URL = '/static/'
150
    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
151
    ALLOWED_HOSTS = ['localhost', '127.0.0.1']
152
153
ADMINS = (
154
    (config.get('admin', 'ADMIN_NAME'), config.get('admin', 'ADMIN_EMAIL'), ),
155
)
156
MANAGERS = ADMINS
157
EMAIL_SUBJECT_PREFIX = '[OpenSubmit] '
158
TIME_ZONE = config.get("server", "TIME_ZONE")
159
LANGUAGE_CODE = 'en-en'
160
USE_I18N = True
161
USE_L10N = True
162
USE_TZ = False
163
STATICFILES_FINDERS = (
164
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
165
    'django.contrib.staticfiles.finders.FileSystemFinder',
166
)
167
SECRET_KEY = config.get("server", "SECRET_KEY")
168
169
TEMPLATES = [
170
    {
171
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
172
        'OPTIONS': {'debug': DEBUG,
173
                    'context_processors':
174
                        ("django.contrib.auth.context_processors.auth",
175
                         "django.template.context_processors.debug",
176
                         "django.template.context_processors.i18n",
177
                         "django.template.context_processors.media",
178
                         "django.template.context_processors.static",
179
                         "django.template.context_processors.tz",
180
                         "django.contrib.messages.context_processors.messages",
181
                         "opensubmit.contextprocessors.footer",
182
                         "django.template.context_processors.request",
183
                         "social_django.context_processors.backends",
184
                         "social_django.context_processors.login_redirect")
185
                    },
186
        'APP_DIRS': True,
187
    },
188
]
189
190
TEST_RUNNER = 'opensubmit.tests.DiscoverRunner'
191
192
MIDDLEWARE_CLASSES = (
193
    'django.middleware.common.CommonMiddleware',
194
    'django.contrib.sessions.middleware.SessionMiddleware',
195
    'django.middleware.csrf.CsrfViewMiddleware',
196
    'django.contrib.auth.middleware.AuthenticationMiddleware',
197
    'django.contrib.auth.middleware.RemoteUserMiddleware',
198
    'django.contrib.messages.middleware.MessageMiddleware',
199
    'social_django.middleware.SocialAuthExceptionMiddleware',
200
    'opensubmit.middleware.CourseRegister'
201
)
202
ROOT_URLCONF = 'opensubmit.urls'
203
WSGI_APPLICATION = 'opensubmit.wsgi.application'
204
INSTALLED_APPS = (
205
    'django.contrib.auth',
206
    'django.contrib.contenttypes',
207
    'django.contrib.sessions',
208
    'django.contrib.messages',
209
    'django.contrib.staticfiles',
210
    'formtools',
211
    'social_django',
212
    'bootstrapform',
213
    'grappelli.dashboard',
214
    'grappelli',
215
    'django.contrib.admin',
216
#    'django.contrib.admin.apps.SimpleAdminConfig',
217
    'opensubmit.app.OpenSubmitConfig',
218
)
219
220
LOG_FILE = config.get('server', 'LOG_FILE')
221
222
LOGGING = {
223
    'version': 1,
224
    'disable_existing_loggers': False,
225
    'filters': {
226
        'require_debug_false': {
227
            '()': 'django.utils.log.RequireDebugFalse'
228
        },
229
        'require_debug_true': {
230
            '()': 'django.utils.log.RequireDebugTrue'
231
        },
232
    },
233
    'formatters': {
234
        'verbose': {
235
            'format': "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s"
236
        },
237
        'simple': {
238
            'format': '%(levelname)s %(message)s'
239
        },
240
    },
241
    'handlers': {
242
        'mail_admins': {
243
            'level': 'ERROR',
244
            'filters': ['require_debug_false'],
245
            'class': 'django.utils.log.AdminEmailHandler'
246
        },
247
        'console': {
248
            'level': 'DEBUG',
249
            'filters': ['require_debug_true'],
250
            'class': 'logging.StreamHandler'
251
        },
252
        'file': {
253
            'level': 'DEBUG',
254
            'class': 'logging.FileHandler',
255
            'formatter': 'verbose',
256
            'filename': LOG_FILE
257
        }
258
    },
259
    'loggers': {
260
        'django.request': {
261
            'handlers': ['mail_admins', 'file'],
262
            'level': 'ERROR',
263
            'propagate': True,
264
        },
265
        'OpenSubmit': {
266
            'handlers': ['console', 'file'],
267
            'level': 'DEBUG',
268
            'propagate': True,
269
        },
270
        'social': {
271
            'handlers': ['console', 'file'],
272
            'level': 'DEBUG',
273
            'propagate': True,
274
        },
275
    }
276
}
277
278
LOGIN_GOOGLE = config.get_bool('login', 'LOGIN_GOOGLE')
279
LOGIN_OPENID = config.get_bool('login', 'LOGIN_OPENID')
280
LOGIN_GITHUB = config.get_bool('login', 'LOGIN_GITHUB')
281
LOGIN_TWITTER = config.get_bool('login', 'LOGIN_TWITTER')
282
LOGIN_SHIB = config.get_bool('login', 'LOGIN_SHIB')
283
284
AUTHENTICATION_BACKENDS = (
285
    'django.contrib.auth.backends.ModelBackend',
286
)
287
288
if LOGIN_GOOGLE:
289
    AUTHENTICATION_BACKENDS += ('social_core.backends.google.GoogleOAuth2',)
290
    SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = config.get("login", "LOGIN_GOOGLE_OAUTH_KEY")
291
    SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = config.get("login", "LOGIN_GOOGLE_OAUTH_SECRET")
292
293
if LOGIN_TWITTER:
294
    AUTHENTICATION_BACKENDS += ('social_core.backends.twitter.TwitterOAuth',)
295
    SOCIAL_AUTH_TWITTER_KEY = config.get("login", "LOGIN_TWITTER_OAUTH_KEY")
296
    SOCIAL_AUTH_TWITTER_SECRET = config.get("login", "LOGIN_TWITTER_OAUTH_SECRET")
297
298
if LOGIN_GITHUB:
299
    AUTHENTICATION_BACKENDS += ('social_core.backends.github.GithubOAuth2',)
300
    SOCIAL_AUTH_GITHUB_KEY = config.get("login", "LOGIN_GITHUB_OAUTH_KEY")
301
    SOCIAL_AUTH_GITHUB_SECRET = config.get("login", "LOGIN_GITHUB_OAUTH_SECRET")
302
303
if LOGIN_OPENID:
304
    AUTHENTICATION_BACKENDS += ('opensubmit.social.open_id.OpenIdAuth',)
305
    LOGIN_DESCRIPTION = config.get('login', 'LOGIN_DESCRIPTION')
306
    OPENID_PROVIDER = config.get('login', 'OPENID_PROVIDER')
307
308
if LOGIN_SHIB:
309
    AUTHENTICATION_BACKENDS += ('opensubmit.social.apache.ModShibAuth',)
310
    LOGIN_SHIB_DESCRIPTION = config.get('login', 'LOGIN_SHIB_DESCRIPTION')
311
312
AUTHENTICATION_BACKENDS += ('opensubmit.social.lti.LtiAuth',)
313
314
SOCIAL_AUTH_URL_NAMESPACE = 'social'
315
SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['next', ]
316
SOCIAL_AUTH_PIPELINE = (
317
    'social_core.pipeline.social_auth.social_details',
318
    'social_core.pipeline.social_auth.social_uid',
319
    'social_core.pipeline.social_auth.auth_allowed',
320
    'social_core.pipeline.social_auth.social_user',
321
    'social_core.pipeline.user.get_username',
322
    'social_core.pipeline.social_auth.associate_by_email',  # Transition for existing users
323
    'social_core.pipeline.user.create_user',
324
    'social_core.pipeline.social_auth.associate_user',
325
    'social_core.pipeline.social_auth.load_extra_data',
326
    'social_core.pipeline.user.user_details'
327
)
328
329
JOB_EXECUTOR_SECRET = config.get("executor", "SHARED_SECRET")
330
assert(JOB_EXECUTOR_SECRET is not "")
331
332
GRAPPELLI_ADMIN_TITLE = "OpenSubmit"
333
GRAPPELLI_SWITCH_USER = True
334
GRAPPELLI_INDEX_DASHBOARD = {
335
    'opensubmit.admin.teacher_backend': 'opensubmit.dashboard.TeacherDashboard'
336
}
337