-
Notifications
You must be signed in to change notification settings - Fork 87
/
__init__.py
802 lines (675 loc) · 30.2 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
import datetime
import logging
import os
import re
import sys
from typing import Any, Dict, List, Tuple # noqa
import sentry_sdk
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from ..utils import helpers # pylint: disable=wrong-import-position # noqa
if sys.version_info < (3, 5):
# Require Python 3.5+
raise Exception("Python 3.5 or higher is required.")
""" !! In production, add a file called secret.py to the settings package that
defines SECRET_KEY, SECRET_DATABASE_URL. !!
SECRET_DATABASE_URL should be of the following form:
postgres://<user>:<password>@<host>/<database>
"""
# Dummy values for development and testing.
# Overridden by the import from secret.py below.
SECRET_DATABASE_URL = None # type: str
MAINTENANCE_MODE = None # type: bool
TJSTAR_MAP = None # type: bool
TWITTER_KEYS = None # type: Dict[str,str]
SENTRY_PUBLIC_DSN = None # type: str
USE_SASL = True
NO_CACHE = False
PARKING_ENABLED = True
PARKING_MAX_ABSENCES = 5
NOMINATIONS_ACTIVE = False
NOMINATION_POSITION = ""
ENABLE_WAITLIST = True
ENABLE_BUS_APP = True
ENABLE_BUS_DRIVER = True
ENABLE_PRE_EIGHTH_REDIRECT = False
NOTIFY_ADMIN_EMAILS = None
IOS_APP_CLIENT_IDS = [] # Attempting to OAuth to an application with one of these client IDs will result in a *special* error message
# See templates/oauth2_provider/authorize.html
ALLOWED_METRIC_SCRAPE_IPS = []
EMERGENCY_MESSAGE = None # type: str
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
#
# In production, Nginx filters requests that are not in this list. If this is
# not done, a notification gets sent whenever someone messes with
# the HTTP Host header.
ALLOWED_HOSTS = ["ion.tjhsst.edu", "198.38.18.250", "localhost", "127.0.0.1"]
# When school is scheduled to start
SCHOOL_START_DATE = datetime.date(2017, 8, 28)
# Dates when hoco starts and ends
HOCO_START_DATE = datetime.date(2017, 10, 2)
HOCO_END_DATE = datetime.date(2017, 10, 14)
PRODUCTION = os.getenv("PRODUCTION", "").upper() == "TRUE"
TRAVIS = os.getenv("TRAVIS", "").upper() == "TRUE"
# FIXME: figure out a less-hacky way to do this.
TESTING = "test" in sys.argv
LOGGING_VERBOSE = PRODUCTION
# Whether to report master password attempts
MASTER_NOTIFY = False
# DEBUG defaults to off in PRODUCTION, on otherwise.
DEBUG = os.getenv("DEBUG", str(not PRODUCTION).upper()) == "TRUE"
# Don't send emails unless we're in production.
EMAIL_ANNOUNCEMENTS = PRODUCTION
SEND_ANNOUNCEMENT_APPROVAL = PRODUCTION
# Whether to force sending emails, even if we aren't in production.
FORCE_EMAIL_SEND = False
# Don't require https for testing.
SESSION_COOKIE_SECURE = PRODUCTION
CSRF_COOKIE_SECURE = PRODUCTION
if not PRODUCTION:
# We don't care about session security when running a testing instance.
SECRET_KEY = "_5kc##e7(!4=4)h4slxlgm010l+43zd_84g@82771ay6no-1&i"
# Trust X-Forwarded-For when testing
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTOCOL", "https")
# Internal IP ranges in production
_internal_ip_list = ["198.38.16.0/20", "2001:468:cc0::/48"]
if not PRODUCTION:
# Additional Internal IP ranges for debugging
_internal_ip_list.extend(["127.0.0.0/8", "10.0.0.0/8"])
INTERNAL_IPS = helpers.GlobList(_internal_ip_list)
# Used for Printing access; FCPS external/internal IP ranges
_tj_ip_list = _internal_ip_list + ["151.188.0.0/18", "151.188.192.0/18", "10.0.0.0/8"]
TJ_IPS = helpers.GlobList(_tj_ip_list)
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# What login_required decorator redirects to
# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
LOGIN_URL = "/login"
LOGIN_REDIRECT_URL = "/"
# Whether to perform an HTTP redirect to append a slash
APPEND_SLASH = False
# Email notifications backend and mailserver configuration
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "mail.tjhsst.edu"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_SUBJECT_PREFIX = "[Ion] "
EMAIL_ANNOUNCEMENTS = True
# Address to send messages from
EMAIL_FROM = "ion-noreply@tjhsst.edu"
# Use PostgreSQL database
DATABASES = {"default": {"ENGINE": "django_prometheus.db.backends.postgresql", "CONN_MAX_AGE": 30}} # type: Dict[str,Dict[str,Any]]
# Address to send feedback messages to
FEEDBACK_EMAIL = "intranet@tjhsst.edu"
# Address to send approval messages to
APPROVAL_EMAIL = "intranet-approval@tjhsst.edu"
FILE_UPLOAD_HANDLERS = ["django.core.files.uploadhandler.MemoryFileUploadHandler", "django.core.files.uploadhandler.TemporaryFileUploadHandler"]
# The maximum number of pages in one document that can be
# printed through the printing functionality (determined through pdfinfo)
PRINTING_PAGES_LIMIT = 15
# The maximum file upload and download size for files
FILES_MAX_UPLOAD_SIZE = 200 * 1024 * 1024
FILES_MAX_DOWNLOAD_SIZE = 200 * 1024 * 1024
# Custom error view for CSRF errors; if unspecified, caught by nginx with a generic error
CSRF_FAILURE_VIEW = "intranet.apps.error.views.handle_csrf_view"
############################################
# OPSEC: GIVE A REASON FOR SILENCING #
# SYSTEM CHECKS IF YOU ADD ONE HERE! #
############################################
SILENCED_SYSTEM_CHECKS = [
# W001 doesn't apply, as we use nginx to handle SecurityMiddleware's functions.
"security.W001",
# Suppress W019, as we use frames in the signage module.
"security.W019",
]
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
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-us"
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = False
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
#
# Not used.
MEDIA_ROOT = os.path.join(os.path.dirname(PROJECT_ROOT), "uploads")
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
#
# Not used.
MEDIA_URL = ""
TEST_RUNNER = "django.test.runner.DiscoverRunner"
# Absolute path to the directory static files should be collected to.
# Don"t put anything in this directory yourself; store your static files
# in apps" "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
#
# This is the folder that Nginx serves as /static in production
STATIC_ROOT = os.path.join(PROJECT_ROOT, "collected_static")
# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = "/static/"
# Additional locations of static files
STATICFILES_DIRS = [
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don"t forget to use absolute paths, not relative paths.
os.path.join(PROJECT_ROOT, "static")
]
# 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",
"pipeline.finders.PipelineFinder",
]
STATICFILES_STORAGE = "pipeline.storage.PipelineStorage"
PIPELINE = {
"CSS_COMPRESSOR": None,
"COMPILERS": ["pipeline.compilers.sass.SASSCompiler"],
"STYLESHEETS": {
"base": {"source_filenames": ["css/base.scss", "css/themes.scss", "css/responsive.scss"], "output_filename": "css/base.css"},
"eighth.admin": {"source_filenames": ["css/eighth.common.scss", "css/eighth.admin.scss"], "output_filename": "css/eighth.admin.css"},
"eighth.signup": {"source_filenames": ["css/eighth.common.scss", "css/eighth.signup.scss"], "output_filename": "css/eighth.signup.css"},
},
} # type: Dict[str,Any]
LIST_OF_INDEPENDENT_CSS = [
"about",
"api",
"login",
"emerg",
"files",
"schedule",
"theme.blue",
"page_base",
"responsive.core",
"search",
"dashboard",
"events",
"schedule.widget",
"dashboard.widgets",
"profile",
"polls",
"groups",
"board",
"announcements.form",
"polls.form",
"preferences",
"signage.base",
"signage.touch",
"signage.touch.landscape",
"eighth.common",
"eighth.attendance",
"eighth.profile",
"eighth.schedule",
"eighth.maintenance",
"lostfound",
"welcome",
"hoco_ribbon",
"hoco_scores",
"oauth",
"bus",
"signage.page",
"courses",
"sessionmgmt",
"dark/base",
"dark/login",
"dark/schedule",
"dark/events",
"dark/dashboard",
"dark/dashboard.widgets",
"dark/schedule.widget",
"dark/nav",
"dark/cke",
"dark/polls",
"dark/bus",
"dark/files",
"dark/welcome",
"dark/preferences",
"dark/about",
"dark/lostfound",
"dark/eighth.signup",
"dark/eighth.attendance",
"dark/select",
"dark/eighth.schedule",
"dark/oauth",
"dark/sessionmgmt",
]
for name in LIST_OF_INDEPENDENT_CSS:
PIPELINE["STYLESHEETS"].update(helpers.single_css_map(name))
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"intranet.apps.auth.backends.MasterPasswordAuthenticationBackend",
"intranet.apps.auth.backends.KerberosAuthenticationBackend",
"oauth2_provider.backends.OAuth2Backend",
)
# Default to Argon2, see https://docs.djangoproject.com/en/dev/topics/auth/passwords/#argon2-usage
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.Argon2PasswordHasher",
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
"django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
"django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
"django.contrib.auth.hashers.BCryptPasswordHasher",
]
# Use the custom User model defined in apps/users/models.py
AUTH_USER_MODEL = "users.User"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": (os.path.join(PROJECT_ROOT, "templates"),),
"OPTIONS": {
"context_processors": (
"django.contrib.auth.context_processors.auth", # Authentication; must be defined first
"django.template.context_processors.debug", # Django default
"django.template.context_processors.request", # Django default
"django.contrib.messages.context_processors.messages", # For page messages
"intranet.apps.context_processors.ion_base_url", # For determining the base url
"intranet.apps.context_processors.nav_categorizer", # For determining the category in the navbar
"intranet.apps.context_processors.global_warning", # For showing a global warning throughout the application (in page_base.html)
"intranet.apps.eighth.context_processors.start_date", # For determining the eighth pd start date
"intranet.apps.eighth.context_processors.absence_count", # For showing the absence count in the navbar
"intranet.apps.eighth.context_processors.enable_waitlist", # For checking if the waitlist is enabled
"intranet.apps.context_processors.mobile_app", # For the custom android app functionality (tbd?)
"intranet.apps.context_processors.is_tj_ip", # Whether on the internal TJ or FCPS network
"intranet.apps.context_processors.show_homecoming", # Sitewide custom themes (special events, etc)
"intranet.apps.context_processors.global_custom_theme", # Sitewide custom themes (special events, etc)
"intranet.apps.context_processors.show_bus_button",
"intranet.apps.context_processors.enable_dark_mode",
"intranet.apps.context_processors.oauth_toolkit", # Django OAuth Toolkit-related middleware
"intranet.apps.context_processors.settings_export", # "Exports" django.conf.settings as DJANGO_SETTINGS
),
"debug": True, # Only enabled if DEBUG is true as well
"loaders": ("django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader"),
"libraries": {"staticfiles": "django.contrib.staticfiles.templatetags.staticfiles"},
},
}
] # type: List[Dict[str,Any]]
if PRODUCTION:
TEMPLATES[0]["OPTIONS"]["loaders"] = [
("django.template.loaders.cached.Loader", ["django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader"])
]
if not PRODUCTION and os.getenv("WARN_INVALID_TEMPLATE_VARS", "NO") == "YES":
TEMPLATES[0]["OPTIONS"]["string_if_invalid"] = helpers.InvalidString("%s")
MIDDLEWARE = [
"intranet.middleware.url_slashes.FixSlashes", # Remove slashes in URLs
"intranet.middleware.same_origin.SameOriginMiddleware", # 401s requests with an "Origin" header that doesn't match the "Host" header
"django_prometheus.middleware.PrometheusBeforeMiddleware", # Django Prometheus initial
"django.middleware.common.CommonMiddleware", # Django default
"django.contrib.sessions.middleware.SessionMiddleware", # Django sessions
"django.middleware.csrf.CsrfViewMiddleware", # Django CSRF
"django.middleware.clickjacking.XFrameOptionsMiddleware", # Django X-Frame-Options
"django.contrib.auth.middleware.AuthenticationMiddleware", # Django auth
"oauth2_provider.middleware.OAuth2TokenMiddleware", # Django Oauth toolkit
"maintenance_mode.middleware.MaintenanceModeMiddleware", # Maintenance mode
"intranet.middleware.threadlocals.ThreadLocalsMiddleware", # Thread locals
"intranet.middleware.traceback.UserTracebackMiddleware", # Include user in traceback
"django.contrib.messages.middleware.MessageMiddleware", # Messages
"django_user_agents.middleware.UserAgentMiddleware",
"intranet.middleware.session_management.SessionManagementMiddleware", # Handles session management (might log the user out, so must be early)
"intranet.middleware.ajax.AjaxNotAuthenticatedMiddleWare", # See note in ajax.py
"intranet.middleware.templates.AdminSelectizeLoadingIndicatorMiddleware", # Selectize fixes
"intranet.middleware.templates.NoReferrerMiddleware", # Prevent malicious JS from changing the referring page
"intranet.middleware.access_log.AccessLogMiddleWare", # Access log
"django_requestlogging.middleware.LogSetupMiddleware", # Request logging
"corsheaders.middleware.CorsMiddleware", # CORS headers, for ext. API use
"simple_history.middleware.HistoryRequestMiddleware",
"django_prometheus.middleware.PrometheusAfterMiddleware", # Django Prometheus after
"intranet.middleware.dark_mode.DarkModeMiddleware", # Dark mode-related middleware
"django_referrer_policy.middleware.ReferrerPolicyMiddleware", # Sets the Referrer-Policy header
]
# URLconf at urls.py
ROOT_URLCONF = "intranet.urls"
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = "intranet.wsgi.application"
# Name of current virtualenv
VIRTUAL_ENV = os.path.basename(os.environ["VIRTUAL_ENV"]) if "VIRTUAL_ENV" in os.environ else "None"
def get_month_seconds():
return datetime.timedelta(hours=24).total_seconds() * 30
CACHE_AGE = {
"dn_id_mapping": int(12 * get_month_seconds()),
"user_grade": int(10 * get_month_seconds()),
"user_classes": int(6 * get_month_seconds()),
"user_photo": int(6 * get_month_seconds()),
"class_teacher": int(6 * get_month_seconds()),
"class_attribute": int(6 * get_month_seconds()),
"user_attribute": int(2 * get_month_seconds()),
"bell_schedule": int(datetime.timedelta(weeks=1).total_seconds()),
"users_list": int(datetime.timedelta(hours=24).total_seconds()),
"printers_list": int(datetime.timedelta(hours=24).total_seconds()),
"emerg": int(datetime.timedelta(minutes=5).total_seconds()),
"sports_school_events": int(datetime.timedelta(hours=1).total_seconds()),
}
if not PRODUCTION and os.getenv("SHORT_CACHE", "NO") == "YES":
# Make the cache age last just long enough to reload the page to
# check if caching worked
for key in CACHE_AGE:
CACHE_AGE[key] = 60
# Cacheops configuration
# may be removed in the future
CACHEOPS_REDIS = {"host": "127.0.0.1", "port": 6379, "db": 1, "socket_timeout": 1}
CACHEOPS = {
"eighth.*": {"timeout": int(datetime.timedelta(hours=24).total_seconds())}, # Only used for caching activity, block lists
"groups.*": {"timeout": int(datetime.timedelta(hours=24).total_seconds())}, # Only used for caching group list
"users.UserDarkModeProperties": {"ops": "get", "timeout": int(datetime.timedelta(minutes=10).total_seconds())},
}
if not TESTING:
# Settings for django-redis-sessions
SESSION_ENGINE = "redis_sessions.session"
SESSION_REDIS_HOST = "127.0.0.1"
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DB = 0
SESSION_REDIS_PREFIX = VIRTUAL_ENV + ":session"
SESSION_COOKIE_AGE = int(datetime.timedelta(hours=2).total_seconds())
SESSION_SAVE_EVERY_REQUEST = True
CACHES = {
"default": {
"OPTIONS": {
# Avoid conflict between production and testing redis db
"DB": (1 if PRODUCTION else 2)
}
}
} # type: Dict[str,Dict[str,Any]]
if TESTING or os.getenv("DUMMY_CACHE", "NO") == "YES" or NO_CACHE:
CACHES["default"] = {"BACKEND": "intranet.utils.cache.DummyCache"}
# extension of django.core.cache.backends.dummy.DummyCache
else:
CACHES["default"] = {
"BACKEND": "redis_cache.RedisCache",
"LOCATION": "127.0.0.1:6379",
"OPTIONS": {"PARSER_CLASS": "redis.connection.HiredisParser", "PICKLE_VERSION": 4},
"KEY_PREFIX": VIRTUAL_ENV,
}
CSL_REALM = "CSL.TJHSST.EDU" # CSL Realm
AD_REALM = "LOCAL.TJHSST.EDU"
KINIT_TIMEOUT = 15 # seconds before pexpect timeouts
FCPS_STUDENT_ID_LENGTH = 7
# Django REST framework configuration
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), # require authentication
"USE_ABSOLUTE_URLS": True,
# Return native `Date` and `Time` objects in `serializer.data`
"DATETIME_FORMAT": None,
"DATE_FORMAT": None,
"TIME_FORMAT": None,
"EXCEPTION_HANDLER": "intranet.apps.api.utils.custom_exception_handler",
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 50,
"DEFAULT_AUTHENTICATION_CLASSES": (
"intranet.apps.api.authentication.ApiBasicAuthentication",
"intranet.apps.api.authentication.CsrfExemptSessionAuthentication", # exempts CSRF checking on API
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
),
}
# Django OAuth Toolkit configuration
OAUTH2_PROVIDER = {
# this is the list of available scopes
"SCOPES": {"read": "Read scope", "write": "Write scope"},
# OAuth refresh tokens expire in 30 days
"REFRESH_TOKEN_EXPIRE_SECONDS": 60 * 60 * 24 * 30,
}
OAUTH2_PROVIDER_APPLICATION_MODEL = "oauth2_provider.Application"
INSTALLED_APPS = [
# internal Django
"django.contrib.auth",
"django.contrib.admin",
"django.contrib.admindocs",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# Django plugins
"django_extensions", # django-extensions
"django_requestlogging", # django-requestlogging-redux
"rest_framework", # django-rest-framework
"maintenance_mode", # django-maintenance-mode
"pipeline", # django-pipeline
"channels",
# Intranet apps
"intranet.apps",
"intranet.apps.announcements",
"intranet.apps.api",
"intranet.apps.auth",
"intranet.apps.bus",
"intranet.apps.eighth",
"intranet.apps.events",
"intranet.apps.groups",
"intranet.apps.search",
"intranet.apps.schedule",
"intranet.apps.notifications",
"intranet.apps.feedback",
"intranet.apps.users",
"intranet.apps.preferences",
"intranet.apps.files",
"intranet.apps.printing",
"intranet.apps.polls",
"intranet.apps.signage",
"intranet.apps.seniors",
"intranet.apps.emerg",
"intranet.apps.itemreg",
"intranet.apps.lostfound",
"intranet.apps.emailfwd",
"intranet.apps.parking",
"intranet.apps.dataimport",
"intranet.apps.nomination",
"intranet.apps.sessionmgmt",
# Django plugins
"widget_tweaks",
"oauth2_provider", # django-oauth-toolkit
"corsheaders", # django-cors-headers
"cacheops", # django-cacheops
"svg", # django-inline-svg
"simple_history", # django-simple-history
"django_referrer_policy",
"django_user_agents",
]
# Django Channels Configuration (we use this for websockets)
CHANNEL_LAYERS = {"default": {"BACKEND": "channels_redis.core.RedisChannelLayer", "CONFIG": {"hosts": [("127.0.0.1", 6379)]}}}
ASGI_APPLICATION = "intranet.routing.application"
# Eighth period default block date format
# Post Django 1.8.7, this can no longer be used in templates.
EIGHTH_BLOCK_DATE_FORMAT = "D, N j, Y"
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOG_LEVEL = "DEBUG" if LOGGING_VERBOSE else "INFO"
if os.getenv("LOG_LEVEL") in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"):
LOG_LEVEL = os.environ["LOG_LEVEL"]
def get_log(name): # pylint: disable=redefined-outer-name; 'name' is used as the target of a for loop, so we can safely override it
return [name] if (PRODUCTION and not TRAVIS) else []
# https://docs.djangoproject.com/en/dev/topics/logging/
LOGGING = {
"version": 1,
"disable_existing_loggers": True,
"formatters": {
"simple": {"format": "%(levelname)s: %(asctime)s - %(remote_addr)s - %(username)s - %(path_info)s\n\t%(message)s"},
"access": {"format": "%(message)s"},
"error": {"format": "%(asctime)s: \n%(message)s"},
},
"filters": {
"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"},
"request": {"()": "django_requestlogging.logging_filters.RequestFilter"},
},
"handlers": {
# Log in console
"console": {"level": "DEBUG", "class": "logging.StreamHandler", "filters": ["request"], "formatter": "simple"},
# Log access in console
"console_access": {"level": "DEBUG", "class": "logging.StreamHandler", "filters": ["request"], "formatter": "access"},
# Log access to file (DEBUG=FALSE)
"access_log": {
"level": "DEBUG",
"filters": ["require_debug_false"],
"class": "logging.handlers.TimedRotatingFileHandler",
"formatter": "access",
"filename": "/var/log/ion/app_access.log",
# Rollover on Sundays; preserve 20 weeks
"when": "W6",
"interval": 1,
"backupCount": 20,
"delay": True,
},
# Log auth to file (DEBUG=FALSE)
"auth_log": {
"level": "DEBUG",
"filters": ["require_debug_false"],
"class": "logging.handlers.TimedRotatingFileHandler",
"formatter": "access",
"filename": "/var/log/ion/app_auth.log",
# Rollover on Sundays; preserve 20 weeks
"when": "W6",
"interval": 1,
"backupCount": 20,
"delay": True,
},
# Log error to file (DEBUG=FALSE)
"error_log": {
"level": "ERROR",
"filters": ["require_debug_false", "request"],
"class": "logging.FileHandler",
"formatter": "error",
"filename": "/var/log/ion/app_error.log",
"delay": True,
},
},
"loggers": {
# Django errors get sent to console and error logfile
"django": {"handlers": ["console"] + get_log("error_log"), "level": "ERROR", "propagate": True},
# Intranet errors go to console and error logfile
"intranet": {"handlers": ["console"] + get_log("error_log"), "level": LOG_LEVEL, "propagate": True},
# Intranet access logs to accesslog
"intranet_access": {"handlers": ["console_access"] + get_log("access_log"), "level": "DEBUG", "propagate": False},
# Intranet auth logs to authlog
"intranet_auth": {"handlers": ["console_access"] + get_log("auth_log"), "level": "DEBUG", "propagate": False},
# errors that relate to sentry
"sentry.errors": {"level": "DEBUG", "handlers": ["console"], "propagate": False},
},
}
# The debug toolbar is always loaded, unless you manually override SHOW_DEBUG_TOOLBAR
SHOW_DEBUG_TOOLBAR = os.getenv("SHOW_DEBUG_TOOLBAR", "YES") == "YES"
if SHOW_DEBUG_TOOLBAR:
DEBUG_TOOLBAR_PATCH_SETTINGS = False
# Boolean value defines whether enabled by default
_panels = [
("debug_toolbar.panels.versions.VersionsPanel", False),
("debug_toolbar.panels.timer.TimerPanel", True),
("debug_toolbar.panels.settings.SettingsPanel", False),
("debug_toolbar.panels.headers.HeadersPanel", False),
("debug_toolbar.panels.request.RequestPanel", False),
("debug_toolbar.panels.sql.SQLPanel", True),
("debug_toolbar.panels.staticfiles.StaticFilesPanel", False),
("debug_toolbar.panels.templates.TemplatesPanel", False),
("debug_toolbar.panels.cache.CachePanel", False),
("debug_toolbar.panels.signals.SignalsPanel", False),
("debug_toolbar.panels.logging.LoggingPanel", True),
("debug_toolbar.panels.redirects.RedirectsPanel", False),
("debug_toolbar.panels.profiling.ProfilingPanel", False),
]
# Only show debug toolbar when requested if in production.
DEBUG_TOOLBAR_CONFIG = {
"DISABLE_PANELS": [panel for panel, enabled in _panels if not enabled],
"SHOW_TOOLBAR_CALLBACK": "intranet.utils.helpers.debug_toolbar_callback",
}
DEBUG_TOOLBAR_PANELS = [t[0] for t in _panels]
# Add middleware
MIDDLEWARE.extend(
[
"intranet.middleware.templates.StripNewlinesMiddleware", # Strip newlines
"debug_toolbar.middleware.DebugToolbarMiddleware", # Debug toolbar
]
)
INSTALLED_APPS += ["debug_toolbar"]
MAINTENANCE_MODE_TEMPLATE = "error/503.html"
MAINTENANCE_MODE_IGNORE_SUPERUSER = True
# Allow *.tjhsst.edu sites to access API, signage, and other resources
CORS_ORIGIN_ALLOW_ALL = False
# Uncomment to only allow XHR on API resources from TJ domains
# CORS_URLS_REGEX = r'^/api/.*$'
# Same origin frame options
X_FRAME_OPTIONS = "SAMEORIGIN"
# X-XSS-Protection: 1; mode=block
# Already set on nginx level
SECURE_BROWSER_XSS_FILTER = True
# To accomodate for the fact that nginx "swallows" https connections
# by forwarding to http://gunicorn
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Add git information for the login page
GIT = {
"commit_short_hash": helpers.get_current_commit_short_hash(PROJECT_ROOT),
"commit_long_hash": helpers.get_current_commit_long_hash(PROJECT_ROOT),
"commit_info": helpers.get_current_commit_info(),
"commit_date": helpers.get_current_commit_date(),
"commit_github_url": helpers.get_current_commit_github_url(PROJECT_ROOT),
}
# Senior graduation year
SENIOR_GRADUATION_YEAR = 2019
# Senior graduation date in Javascript-readable format
SENIOR_GRADUATION = datetime.datetime(year=SENIOR_GRADUATION_YEAR, month=6, day=18, hour=19).strftime("%B %d %Y %H:%M:%S")
# Month (1-indexed) after which a new school year begins
# July = 7
YEAR_TURNOVER_MONTH = 7
# The hour on an eighth period day to lock teachers from
# taking attendance (10PM)
ATTENDANCE_LOCK_HOUR = 22
# The number of days to show an absence message (2 weeks)
CLEAR_ABSENCE_DAYS = 14
# The address for FCPS' Emergency Announcement page
FCPS_EMERGENCY_PAGE = "https://www.fcps.edu/alert_msg_feed" # type: str
# The timeout for the request to FCPS' emergency page (in seconds)
FCPS_EMERGENCY_TIMEOUT = 5
# Show an iframe with tjStar activity data
if TJSTAR_MAP is None:
TJSTAR_MAP = False
SIMILAR_THRESHOLD = 5
# Substrings of user agents to not log in the Ion access logs
NONLOGGABLE_USER_AGENT_SUBSTRINGS = ["Prometheus", "GoogleBot", "UptimeRobot"]
# The location of the Celery broker (message transport)
CELERY_BROKER_URL = "amqp://localhost"
CELERY_ACCEPT_CONTENT = ["json", "pickle"]
CELERY_TASK_SERIALIZER = "pickle"
MAINTENANCE_MODE = False
# Django User Agents configuration
USER_AGENTS_CACHE = 'default'
# The Referrer-policy header
REFERRER_POLICY = "strict-origin-when-cross-origin"
REAUTHENTICATION_EXPIRE_TIMEOUT = 2 * 60 * 60 # seconds
# Shows a warning message with yellow background on the login page
# LOGIN_WARNING = "This is a message to display on the login page."
# Shows a warning message with yellow background on the login and all interior pages
# GLOBAL_WARNING = "This is a message to display throughout the application."
try:
from .secret import * # noqa
except ImportError:
pass
# In-memory sqlite3 databases significantly speed up running tests.
if TESTING:
DATABASES["default"]["ENGINE"] = "django.db.backends.sqlite3"
DATABASES["default"]["NAME"] = ":memory:"
# Horrible hack to suppress all migrations to speed up the tests.
MIGRATION_MODULES = helpers.MigrationMock()
# FIXME: we really shouldn't have to do this.
LOGGING_VERBOSE = re.search("-v ?[2-3]|--verbosity [2-3]", " ".join(sys.argv)) is not None
elif PRODUCTION or SECRET_DATABASE_URL is not None:
DATABASES["default"].update(helpers.parse_db_url(SECRET_DATABASE_URL))
else:
# Default testing db config.
DATABASES["default"].update({"NAME": "ion", "USER": "ion", "PASSWORD": "pwd"})
# Set up sentry logging
if PRODUCTION:
# This is implicitly set up but we do this just in case
sentry_logging = LoggingIntegration(
level=logging.INFO, event_level=logging.ERROR # Capture info and above as breadcrumbs # Send errors as events
)
sentry_sdk.init(SENTRY_PUBLIC_DSN, integrations=[DjangoIntegration(), sentry_logging, CeleryIntegration()], send_default_pii=True)