settings.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. """
  2. Django settings for glitchtip project.
  3. Generated by 'django-admin startproject' using Django 3.0rc1.
  4. For more information on this file, see
  5. https://docs.djangoproject.com/en/dev/topics/settings/
  6. For the full list of settings and their values, see
  7. https://docs.djangoproject.com/en/dev/ref/settings/
  8. """
  9. import logging
  10. import os
  11. import sys
  12. import warnings
  13. from datetime import timedelta
  14. import environ
  15. import sentry_sdk
  16. from celery.schedules import crontab
  17. from django.core.exceptions import ImproperlyConfigured
  18. from sentry_sdk.integrations.django import DjangoIntegration
  19. from whitenoise.storage import CompressedManifestStaticFilesStorage
  20. env = environ.Env(
  21. ALLOWED_HOSTS=(list, ["*"]),
  22. AWS_ACCESS_KEY_ID=(str, None),
  23. AWS_SECRET_ACCESS_KEY=(str, None),
  24. AWS_STORAGE_BUCKET_NAME=(str, None),
  25. AWS_S3_ENDPOINT_URL=(str, None),
  26. AWS_LOCATION=(str, ""),
  27. DEBUG=(bool, False),
  28. DEBUG_TOOLBAR=(bool, False),
  29. STATIC_URL=(str, "/"),
  30. STATICFILES_STORAGE=(
  31. str,
  32. "glitchtip.settings.NoSourceMapsStorage",
  33. ),
  34. )
  35. path = environ.Path()
  36. # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
  37. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  38. # Quick-start development settings - unsuitable for production
  39. # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
  40. # SECURITY WARNING: keep the secret key used in production secret!
  41. SECRET_KEY = env.str("SECRET_KEY", "change_me")
  42. # SECURITY WARNING: don't run with debug turned on in production!
  43. DEBUG = env("DEBUG")
  44. # Enable only for running end to end testing. Debug must be True to use.
  45. ENABLE_TEST_API = env.bool("ENABLE_TEST_API", False)
  46. if DEBUG is False:
  47. ENABLE_TEST_API = False
  48. ALLOWED_HOSTS = env("ALLOWED_HOSTS")
  49. # Necessary for kubernetes health checks
  50. POD_IP = env.str("POD_IP", default=None)
  51. if POD_IP:
  52. ALLOWED_HOSTS.append(POD_IP)
  53. ENVIRONMENT = env.str("ENVIRONMENT", None)
  54. GLITCHTIP_VERSION = env.str("GLITCHTIP_VERSION", None)
  55. # Used in email and DSN generation. Set to full domain such as https://glitchtip.example.com
  56. default_url = env.str(
  57. "APP_URL", env.str("GLITCHTIP_DOMAIN", "http://localhost:8000")
  58. ) # DigitalOcean App Platform uses APP_URL
  59. GLITCHTIP_URL = env.url("GLITCHTIP_URL", default_url)
  60. if GLITCHTIP_URL.scheme not in ["http", "https"]:
  61. raise ImproperlyConfigured("GLITCHTIP_DOMAIN must start with http or https")
  62. # Events and associated data older than this will be deleted from the database
  63. GLITCHTIP_MAX_EVENT_LIFE_DAYS = env.int("GLITCHTIP_MAX_EVENT_LIFE_DAYS", default=90)
  64. # For development purposes only, prints out inbound event store json
  65. EVENT_STORE_DEBUG = env.bool("EVENT_STORE_DEBUG", False)
  66. # Throttle % of all transaction events. Not intended for general use. May change without warning.
  67. THROTTLE_TRANSACTION_EVENTS = env.float("THROTTLE_TRANSACTION_EVENTS", None)
  68. GLITCHTIP_ENABLE_DIFS = env.bool("GLITCHTIP_ENABLE_DIFS", False)
  69. # GlitchTip can track GlitchTip's own errors.
  70. # If enabling this, use a different server to avoid infinite loops.
  71. def before_send(event, hint):
  72. """Don't log django.DisallowedHost errors in Sentry."""
  73. if "log_record" in hint:
  74. if hint["log_record"].name == "django.security.DisallowedHost":
  75. return None
  76. return event
  77. SENTRY_DSN = env.str("SENTRY_DSN", None)
  78. # Optionally allow a different DSN for the frontend
  79. SENTRY_FRONTEND_DSN = env.str("SENTRY_FRONTEND_DSN", SENTRY_DSN)
  80. # Set traces_sample_rate to 1.0 to capture 100%. Recommended to keep this value low.
  81. # Disabled by default
  82. SENTRY_TRACES_SAMPLE_RATE = env.float("SENTRY_TRACES_SAMPLE_RATE", None)
  83. if SENTRY_DSN:
  84. release = "glitchtip@" + GLITCHTIP_VERSION if GLITCHTIP_VERSION else None
  85. sentry_sdk.init(
  86. dsn=SENTRY_DSN,
  87. integrations=[DjangoIntegration()],
  88. before_send=before_send,
  89. release=release,
  90. environment=ENVIRONMENT,
  91. auto_session_tracking=False,
  92. traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
  93. )
  94. def show_toolbar(request):
  95. return env("DEBUG_TOOLBAR")
  96. DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}
  97. DEBUG_TOOLBAR_PANELS = [
  98. "debug_toolbar.panels.versions.VersionsPanel",
  99. "debug_toolbar.panels.timer.TimerPanel",
  100. "debug_toolbar.panels.settings.SettingsPanel",
  101. "debug_toolbar.panels.headers.HeadersPanel",
  102. "debug_toolbar.panels.request.RequestPanel",
  103. "debug_toolbar.panels.sql.SQLPanel",
  104. ]
  105. # Application definition
  106. INSTALLED_APPS = [
  107. "django_rest_mfa.mfa_admin",
  108. "django.contrib.admin",
  109. "django.contrib.auth",
  110. "django.contrib.contenttypes",
  111. "django.contrib.sessions",
  112. "django.contrib.messages",
  113. "django.contrib.staticfiles",
  114. "django.contrib.sites",
  115. "django.contrib.postgres",
  116. "allauth",
  117. "allauth.account",
  118. "allauth.socialaccount",
  119. "allauth.socialaccount.providers.digitalocean",
  120. "allauth.socialaccount.providers.gitea",
  121. "allauth.socialaccount.providers.github",
  122. "allauth.socialaccount.providers.gitlab",
  123. "allauth.socialaccount.providers.google",
  124. "allauth.socialaccount.providers.microsoft",
  125. "allauth.socialaccount.providers.nextcloud",
  126. "anymail",
  127. "corsheaders",
  128. "django_celery_results",
  129. "django_filters",
  130. "django_extensions",
  131. "django_rest_mfa",
  132. "debug_toolbar",
  133. "rest_framework",
  134. "drf_yasg",
  135. "dj_rest_auth",
  136. "dj_rest_auth.registration",
  137. "storages",
  138. "glitchtip",
  139. "alerts",
  140. "api_tokens",
  141. "environments",
  142. "files",
  143. "organizations_ext",
  144. "events",
  145. "issues",
  146. "users",
  147. "user_reports",
  148. "glitchtip.uptime",
  149. "performance",
  150. "projects",
  151. "teams",
  152. "releases",
  153. "difs",
  154. ]
  155. # Ensure no one uses runsslserver in production
  156. if SECRET_KEY == "change_me" and DEBUG is True:
  157. INSTALLED_APPS += ["sslserver"]
  158. MIDDLEWARE = [
  159. "django.middleware.security.SecurityMiddleware",
  160. "django.contrib.sessions.middleware.SessionMiddleware",
  161. "corsheaders.middleware.CorsMiddleware",
  162. "csp.middleware.CSPMiddleware",
  163. "django.middleware.clickjacking.XFrameOptionsMiddleware",
  164. "whitenoise.middleware.WhiteNoiseMiddleware",
  165. "debug_toolbar.middleware.DebugToolbarMiddleware",
  166. "django.middleware.common.CommonMiddleware",
  167. "django.middleware.csrf.CsrfViewMiddleware",
  168. "django.contrib.auth.middleware.AuthenticationMiddleware",
  169. "django.contrib.messages.middleware.MessageMiddleware",
  170. "django.middleware.clickjacking.XFrameOptionsMiddleware",
  171. "sentry.middleware.proxy.DecompressBodyMiddleware",
  172. ]
  173. ROOT_URLCONF = "glitchtip.urls"
  174. TEMPLATES = [
  175. {
  176. "BACKEND": "django.template.backends.django.DjangoTemplates",
  177. "DIRS": [path("dist"), path("templates")],
  178. "APP_DIRS": True,
  179. "OPTIONS": {
  180. "context_processors": [
  181. "django.template.context_processors.debug",
  182. "django.template.context_processors.request",
  183. "django.contrib.auth.context_processors.auth",
  184. "django.contrib.messages.context_processors.messages",
  185. ],
  186. },
  187. },
  188. ]
  189. WSGI_APPLICATION = "glitchtip.wsgi.application"
  190. CORS_ORIGIN_ALLOW_ALL = env.bool("CORS_ORIGIN_ALLOW_ALL", True)
  191. CORS_ORIGIN_WHITELIST = env.tuple("CORS_ORIGIN_WHITELIST", str, default=())
  192. SECURE_BROWSER_XSS_FILTER = True
  193. CSP_DEFAULT_SRC = env.list("CSP_DEFAULT_SRC", str, ["'self'"])
  194. CSP_STYLE_SRC = env.list(
  195. "CSP_STYLE_SRC", str, ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"]
  196. )
  197. CSP_STYLE_SRC_ELEM = env.list(
  198. "CSP_STYLE_SRC_ELEM",
  199. str,
  200. ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
  201. )
  202. CSP_FONT_SRC = env.list(
  203. "CSP_FONT_SRC", str, ["'self'", "https://fonts.gstatic.com", "data:"]
  204. )
  205. # Redoc requires blob
  206. CSP_WORKER_SRC = env.list("CSP_WORKER_SRC", str, ["'self'", "blob:"])
  207. # GlitchTip can record it's own errors
  208. CSP_CONNECT_SRC = env.list(
  209. "CSP_CONNECT_SRC", str, ["'self'", "https://*.glitchtip.com"]
  210. )
  211. # Needed for Analytics and Stripe for SaaS use cases. Both are disabled by default.
  212. CSP_SCRIPT_SRC = env.list(
  213. "CSP_SCRIPT_SRC",
  214. str,
  215. ["'self'", "https://*.glitchtip.com", "https://js.stripe.com"],
  216. )
  217. CSP_IMG_SRC = env.list("CSP_IMG_SRC", str, ["'self'"])
  218. CSP_FRAME_SRC = env.list("CSP_FRAME_SRC", str, ["'self'", "https://js.stripe.com"])
  219. # Consider tracking CSP reports with GlitchTip itself
  220. CSP_REPORT_URI = env.tuple("CSP_REPORT_URI", str, None)
  221. CSP_REPORT_ONLY = env.bool("CSP_REPORT_ONLY", False)
  222. SECURE_HSTS_SECONDS = env.int("SECURE_HSTS_SECONDS", 0)
  223. SECURE_HSTS_PRELOAD = env.bool("SECURE_HSTS_PRELOAD", False)
  224. SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool("SECURE_HSTS_INCLUDE_SUBDOMAINS", False)
  225. SESSION_COOKIE_SECURE = env.bool("SESSION_COOKIE_SECURE", False)
  226. SESSION_COOKIE_SAMESITE = env.str("SESSION_COOKIE_SAMESITE", "Lax")
  227. DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL", "webmaster@localhost")
  228. ANYMAIL = {
  229. "MAILGUN_API_KEY": env.str("MAILGUN_API_KEY", None),
  230. "MAILGUN_SENDER_DOMAIN": env.str("MAILGUN_SENDER_DOMAIN", None),
  231. "MAILGUN_API_URL": env.str("MAILGUN_API_URL", "https://api.mailgun.net/v3"),
  232. }
  233. ACCOUNT_EMAIL_SUBJECT_PREFIX = ""
  234. # Database
  235. # https://docs.djangoproject.com/en/dev/ref/settings/#databases
  236. DATABASES = {
  237. "default": env.db(default="postgres://postgres:postgres@postgres:5432/postgres")
  238. }
  239. DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
  240. # We need to support both url and broken out host to support helm redis chart
  241. REDIS_HOST = env.str("REDIS_HOST", None)
  242. if REDIS_HOST:
  243. REDIS_PORT = env.str("REDIS_PORT", "6379")
  244. REDIS_DATABASE = env.str("REDIS_DATABASE", "0")
  245. REDIS_PASSWORD = env.str("REDIS_PASSWORD", None)
  246. if REDIS_PASSWORD:
  247. REDIS_URL = (
  248. f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DATABASE}"
  249. )
  250. else:
  251. REDIS_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DATABASE}"
  252. else:
  253. REDIS_URL = env.str("REDIS_URL", "redis://redis:6379/0")
  254. CELERY_BROKER_URL = REDIS_URL
  255. CELERY_BROKER_TRANSPORT_OPTIONS = {
  256. "fanout_prefix": True,
  257. "fanout_patterns": True,
  258. }
  259. CELERY_RESULT_BACKEND = "django-db"
  260. CELERY_CACHE_BACKEND = "django-cache"
  261. CELERY_BEAT_SCHEDULE = {
  262. "send-alert-notifications": {
  263. "task": "alerts.tasks.process_event_alerts",
  264. "schedule": 60,
  265. },
  266. "cleanup-old-events": {
  267. "task": "issues.tasks.cleanup_old_events",
  268. "schedule": crontab(hour=6, minute=1),
  269. },
  270. "cleanup-old-transaction-events": {
  271. "task": "performance.tasks.cleanup_old_transaction_events",
  272. "schedule": crontab(hour=6, minute=10),
  273. },
  274. "cleanup-old-monitor-checks": {
  275. "task": "glitchtip.uptime.tasks.cleanup_old_monitor_checks",
  276. "schedule": crontab(hour=6, minute=20),
  277. },
  278. "uptime-dispatch-checks": {
  279. "task": "glitchtip.uptime.tasks.dispatch_checks",
  280. "schedule": timedelta(seconds=30),
  281. },
  282. }
  283. CACHES = {
  284. "default": {
  285. "BACKEND": "django.core.cache.backends.redis.RedisCache",
  286. "LOCATION": REDIS_URL,
  287. }
  288. }
  289. # Password validation
  290. # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
  291. AUTH_PASSWORD_VALIDATORS = [
  292. {
  293. "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
  294. },
  295. {
  296. "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
  297. },
  298. {
  299. "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
  300. },
  301. {
  302. "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
  303. },
  304. ]
  305. # Internationalization
  306. # https://docs.djangoproject.com/en/dev/topics/i18n/
  307. LANGUAGE_CODE = "en-us"
  308. TIME_ZONE = "UTC"
  309. USE_I18N = True
  310. USE_L10N = True
  311. USE_TZ = True
  312. SITE_ID = 1
  313. # Static files (CSS, JavaScript, Images)
  314. # https://docs.djangoproject.com/en/dev/howto/static-files/
  315. STATIC_URL = "/static/"
  316. AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID")
  317. AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY")
  318. AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
  319. AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL")
  320. AWS_LOCATION = env("AWS_LOCATION")
  321. if AWS_S3_ENDPOINT_URL:
  322. MEDIA_URL = env.str(
  323. "MEDIA_URL", "https://%s/%s/" % (AWS_S3_ENDPOINT_URL, AWS_LOCATION)
  324. )
  325. DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
  326. else:
  327. MEDIA_URL = "media/"
  328. MEDIA_ROOT = env.str("MEDIA_ROOT", "")
  329. STATICFILES_DIRS = [
  330. "assets",
  331. "dist",
  332. ]
  333. STATIC_ROOT = path("static/")
  334. STATICFILES_STORAGE = env("STATICFILES_STORAGE")
  335. EMAIL_BACKEND = env.str(
  336. "EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend"
  337. )
  338. if os.getenv("EMAIL_URL"):
  339. EMAIL_CONFIG = env.email_url("EMAIL_URL")
  340. vars().update(EMAIL_CONFIG)
  341. AUTH_USER_MODEL = "users.User"
  342. ACCOUNT_AUTHENTICATION_METHOD = "email"
  343. ACCOUNT_EMAIL_REQUIRED = True
  344. ACCOUNT_USERNAME_REQUIRED = False
  345. ACCOUNT_USER_MODEL_USERNAME_FIELD = None
  346. ACCOUNT_ADAPTER = "glitchtip.social.MFAAccountAdapter"
  347. INVITATION_BACKEND = "organizations_ext.invitation_backend.InvitationBackend"
  348. SOCIALACCOUNT_PROVIDERS = {}
  349. GITLAB_URL = env.url("SOCIALACCOUNT_PROVIDERS_gitlab_GITLAB_URL", None)
  350. if GITLAB_URL:
  351. SOCIALACCOUNT_PROVIDERS["gitlab"] = {"GITLAB_URL": GITLAB_URL.geturl()}
  352. GITEA_URL = env.url("SOCIALACCOUNT_PROVIDERS_gitea_GITEA_URL", None)
  353. if GITEA_URL:
  354. SOCIALACCOUNT_PROVIDERS["gittea"] = {"GITEA_URL": GITEA_URL.geturl()}
  355. NEXTCLOUD_URL = env.url("SOCIALACCOUNT_PROVIDERS_nextcloud_SERVER", None)
  356. if NEXTCLOUD_URL:
  357. SOCIALACCOUNT_PROVIDERS["nextcloud"] = {"SERVER": NEXTCLOUD_URL.geturl()}
  358. OLD_PASSWORD_FIELD_ENABLED = True
  359. LOGOUT_ON_PASSWORD_CHANGE = False
  360. REST_AUTH_SERIALIZERS = {
  361. "USER_DETAILS_SERIALIZER": "users.serializers.UserSerializer",
  362. "TOKEN_SERIALIZER": "users.serializers.NoopTokenSerializer",
  363. "PASSWORD_RESET_SERIALIZER": "users.serializers.PasswordSetResetSerializer",
  364. }
  365. REST_AUTH_REGISTER_SERIALIZERS = {
  366. "REGISTER_SERIALIZER": "users.serializers.RegisterSerializer",
  367. }
  368. REST_AUTH_TOKEN_MODEL = None
  369. REST_AUTH_TOKEN_CREATOR = "users.utils.noop_token_creator"
  370. # By default (False) only the first user, superuser, or organization owners may register
  371. # and create an organization. Other users must be invited. Intended for private instances
  372. ENABLE_OPEN_USER_REGISTRATION = env.bool("ENABLE_OPEN_USER_REGISTRATION", False)
  373. AUTHENTICATION_BACKENDS = (
  374. # Needed to login by username in Django admin, regardless of `allauth`
  375. "django.contrib.auth.backends.ModelBackend",
  376. # `allauth` specific authentication methods, such as login by e-mail
  377. "allauth.account.auth_backends.AuthenticationBackend",
  378. )
  379. DEFAULT_RENDERER_CLASSES = ("rest_framework.renderers.JSONRenderer",)
  380. if DEBUG:
  381. DEFAULT_RENDERER_CLASSES = DEFAULT_RENDERER_CLASSES + (
  382. "rest_framework.renderers.BrowsableAPIRenderer",
  383. )
  384. REST_FRAMEWORK = {
  385. "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
  386. "DEFAULT_PAGINATION_CLASS": "glitchtip.pagination.LinkHeaderPagination",
  387. "PAGE_SIZE": 50,
  388. "ORDERING_PARAM": "sort",
  389. "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
  390. "DEFAULT_RENDERER_CLASSES": DEFAULT_RENDERER_CLASSES,
  391. "DEFAULT_AUTHENTICATION_CLASSES": [
  392. "rest_framework.authentication.SessionAuthentication",
  393. "glitchtip.authentication.BearerTokenAuthentication",
  394. ],
  395. "DEFAULT_THROTTLE_RATES": {"anon": "100/minute"},
  396. }
  397. DRF_YASG_EXCLUDE_VIEWS = [
  398. "users.views.SocialAccountDisconnectView",
  399. ]
  400. SWAGGER_SETTINGS = {
  401. "DEFAULT_AUTO_SCHEMA_CLASS": "glitchtip.yasg.SquadSwaggerAutoSchema",
  402. }
  403. LOGGING_HANDLER_CLASS = env.str("DJANGO_LOGGING_HANDLER_CLASS", "logging.StreamHandler")
  404. LOGGING = {
  405. "version": 1,
  406. "disable_existing_loggers": False,
  407. "handlers": {
  408. "null": {
  409. "class": "logging.NullHandler",
  410. },
  411. "console": {
  412. "class": LOGGING_HANDLER_CLASS,
  413. },
  414. },
  415. "loggers": {
  416. "django.security.DisallowedHost": {
  417. "handlers": ["null"],
  418. "propagate": False,
  419. },
  420. },
  421. "root": {"handlers": ["console"]},
  422. }
  423. if LOGGING_HANDLER_CLASS is not logging.StreamHandler:
  424. from celery.signals import after_setup_logger, after_setup_task_logger
  425. @after_setup_logger.connect
  426. @after_setup_task_logger.connect
  427. def setup_celery_logging(logger, **kwargs):
  428. from django.utils.module_loading import import_string
  429. handler = import_string(LOGGING_HANDLER_CLASS)
  430. for h in logger.handlers:
  431. logger.removeHandler(h)
  432. logger.addHandler(handler())
  433. def organization_request_callback(request):
  434. """Gets an organization instance from the id passed through ``request``"""
  435. user = request.user
  436. if user:
  437. return user.organizations_ext_organization.filter(
  438. owner__organization_user__user=user
  439. ).first()
  440. # Set to track activity with Plausible
  441. PLAUSIBLE_URL = env.str("PLAUSIBLE_URL", default=None)
  442. PLAUSIBLE_DOMAIN = env.str("PLAUSIBLE_DOMAIN", default=None)
  443. # Set to chatwoot website token to enable live help widget. Assumes app.chatwoot.com.
  444. CHATWOOT_WEBSITE_TOKEN = env.str("CHATWOOT_WEBSITE_TOKEN", None)
  445. # Is running unit test
  446. TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
  447. # See https://liberapay.com/GlitchTip/donate - suggested self-host donation is $5/month/user.
  448. # Support plans available. Email info@burkesoftware.com for more info.
  449. I_PAID_FOR_GLITCHTIP = env.bool("I_PAID_FOR_GLITCHTIP", False)
  450. # Max events per month for free tier
  451. BILLING_FREE_TIER_EVENTS = env.int("BILLING_FREE_TIER_EVENTS", 1000)
  452. DJSTRIPE_SUBSCRIBER_MODEL = "organizations_ext.Organization"
  453. DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK = organization_request_callback
  454. DJSTRIPE_USE_NATIVE_JSONFIELD = True
  455. DJSTRIPE_FOREIGN_KEY_TO_FIELD = "djstripe_id"
  456. STRIPE_AUTOMATIC_TAX = env.bool("STRIPE_AUTOMATIC_TAX", False)
  457. BILLING_ENABLED = False
  458. STRIPE_LIVE_MODE = env.bool("STRIPE_LIVE_MODE", False)
  459. if env.str("STRIPE_TEST_PUBLIC_KEY", None) or env.str("STRIPE_LIVE_PUBLIC_KEY", None):
  460. BILLING_ENABLED = True
  461. I_PAID_FOR_GLITCHTIP = True
  462. INSTALLED_APPS.append("djstripe")
  463. INSTALLED_APPS.append("djstripe_ext")
  464. STRIPE_TEST_PUBLIC_KEY = env.str("STRIPE_TEST_PUBLIC_KEY", None)
  465. STRIPE_TEST_SECRET_KEY = env.str("STRIPE_TEST_SECRET_KEY", None)
  466. STRIPE_LIVE_PUBLIC_KEY = env.str("STRIPE_LIVE_PUBLIC_KEY", None)
  467. STRIPE_LIVE_SECRET_KEY = env.str("STRIPE_LIVE_SECRET_KEY", None)
  468. DJSTRIPE_WEBHOOK_SECRET = env.str("DJSTRIPE_WEBHOOK_SECRET", None)
  469. CELERY_BEAT_SCHEDULE["set-organization-throttle"] = {
  470. "task": "organizations_ext.tasks.set_organization_throttle",
  471. "schedule": crontab(hour=7, minute=1),
  472. }
  473. CELERY_BEAT_SCHEDULE["warn-organization-throttle"] = {
  474. "task": "djstripe_ext.tasks.warn_organization_throttle",
  475. "schedule": crontab(minute=30),
  476. }
  477. elif TESTING:
  478. # Must run tests with djstripe enabled
  479. BILLING_ENABLED = True
  480. INSTALLED_APPS.append("djstripe")
  481. INSTALLED_APPS.append("djstripe_ext")
  482. STRIPE_TEST_PUBLIC_KEY = "fake"
  483. STRIPE_TEST_SECRET_KEY = "sk_test_fake" # nosec
  484. DJSTRIPE_WEBHOOK_SECRET = "whsec_fake" # nosec
  485. logging.disable(logging.WARNING)
  486. CELERY_TASK_ALWAYS_EAGER = env.bool("CELERY_TASK_ALWAYS_EAGER", False)
  487. if TESTING:
  488. CELERY_TASK_ALWAYS_EAGER = True
  489. STATICFILES_STORAGE = None
  490. # https://github.com/evansd/whitenoise/issues/215
  491. warnings.filterwarnings(
  492. "ignore", message="No directory at", module="whitenoise.base"
  493. )
  494. if CELERY_TASK_ALWAYS_EAGER:
  495. CACHES = {
  496. "default": {
  497. "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
  498. }
  499. }
  500. MFA_SERVER_NAME = "GlitchTip"
  501. FIDO_SERVER_ID = GLITCHTIP_URL.hostname
  502. # Workaround for error encountered at build time (source: https://github.com/axnsan12/drf-yasg/issues/761#issuecomment-1014530805)
  503. class NoSourceMapsStorage(CompressedManifestStaticFilesStorage):
  504. patterns = (
  505. (
  506. "*.css",
  507. (
  508. "(?P<matched>url\\(['\"]{0,1}\\s*(?P<url>.*?)[\"']{0,1}\\))",
  509. (
  510. "(?P<matched>@import\\s*[\"']\\s*(?P<url>.*?)[\"'])",
  511. '@import url("%(url)s")',
  512. ),
  513. ),
  514. ),
  515. )