Passed
Push — master ( a505d5...19065f )
by Peter
01:38
created

Config.has_option()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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