Coverage for sonar_backend/settings.py: 91%

67 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-10 03:22 +0000

1""" 

2Django settings for sonar_backend project. 

3 

4Generated by 'django-admin startproject' using Django 4.2.4. 

5 

6For more information on this file, see 

7https://docs.djangoproject.com/en/4.2/topics/settings/ 

8 

9For the full list of settings and their values, see 

10https://docs.djangoproject.com/en/4.2/ref/settings/ 

11""" 

12 

13from datetime import datetime 

14import logging 

15import os 

16from pathlib import Path 

17 

18import environ 

19 

20from sonar_backend.utils import CustomisedJSONFormatter 

21 

22# Initialise environment variables 

23 

24env = environ.Env( 

25 DEBUG=(bool, False), 

26 POSTGRES_USER=str, 

27 POSTGRES_PASSWORD=str, 

28 POSTGRES_DB=str, 

29 POSTGRES_HOST=str, 

30 POSTGRES_PORT=str, 

31 SECRET_KEY=str, 

32 SONAR_DATA_ENTRY_FOLDER=(str, None), 

33 SONAR_DATA_PROCESSING_FOLDER=(str, None), 

34 SONAR_DATA_ARCHIVE=(str, None), 

35 REDIS_URL=(str, None), 

36 ALLOWED_HOSTS=(str, None), 

37 CORS_ALLOWED_ORIGINS=(str, "http://localhost:5173"), 

38 SAMPLE_BATCH_SIZE=(int, 10), 

39 PROPERTY_BATCH_SIZE=(int, 1000), 

40 PROFILE_IMPORT=(bool, False), 

41 KEEP_IMPORTED_DATA_FILES=(bool, False), 

42) 

43 

44# Build paths inside the project like this: BASE_DIR / 'subdir'. 

45BASE_DIR = Path(__file__).resolve().parent.parent 

46PROJECT_ROOT = Path(__file__).resolve().parent 

47STATIC_ROOT = BASE_DIR / "sonar_server/test/static" 

48 

49# Take environment variables from .env file 

50environ.Env.read_env(BASE_DIR / ".env") 

51 

52# Quick-start development settings - unsuitable for production 

53# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ 

54 

55# SECURITY WARNING: keep the secret key used in production secret! 

56 

57SECRET_KEY = env("SECRET_KEY") 

58 

59# SECURITY WARNING: don't run with debug turned on in production! 

60DEBUG = env("DEBUG") 

61# DEBUG = True 

62 

63ALLOWED_HOSTS = env("ALLOWED_HOSTS", "").split(",") 

64 

65# Application definition 

66 

67INSTALLED_APPS = [ 

68 "corsheaders", 

69 "django.contrib.admin", 

70 "django.contrib.auth", 

71 "django.contrib.contenttypes", 

72 "django.contrib.sessions", 

73 "django.contrib.messages", 

74 "django.contrib.staticfiles", 

75 "environ", 

76 "rest_api.apps.RestApiConfig", 

77 "rest_framework", 

78 "django_filters", 

79 "django_apscheduler", 

80] 

81REST_FRAMEWORK = { 

82 "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", 

83 "PAGE_SIZE": 100, 

84} 

85MIDDLEWARE = [ 

86 "django.middleware.security.SecurityMiddleware", 

87 "whitenoise.middleware.WhiteNoiseMiddleware", 

88 "django.contrib.sessions.middleware.SessionMiddleware", 

89 "corsheaders.middleware.CorsMiddleware", 

90 "django.middleware.common.CommonMiddleware", 

91 "django.middleware.csrf.CsrfViewMiddleware", 

92 "django.contrib.auth.middleware.AuthenticationMiddleware", 

93 "django.contrib.messages.middleware.MessageMiddleware", 

94 "django.middleware.clickjacking.XFrameOptionsMiddleware", 

95] 

96ROOT_URLCONF = "sonar_backend.urls" 

97CORS_ALLOWED_ORIGINS = [ 

98 origin.strip() 

99 for origin in env("CORS_ALLOWED_ORIGINS", default="http://localhost:5173").split( 

100 "," 

101 ) 

102 if origin.strip() 

103] 

104TEMPLATES = [ 

105 { 

106 "BACKEND": "django.template.backends.django.DjangoTemplates", 

107 "DIRS": [], 

108 "APP_DIRS": True, 

109 "OPTIONS": { 

110 "context_processors": [ 

111 "django.template.context_processors.debug", 

112 "django.template.context_processors.request", 

113 "django.contrib.auth.context_processors.auth", 

114 "django.contrib.messages.context_processors.messages", 

115 ], 

116 }, 

117 }, 

118] 

119 

120WSGI_APPLICATION = "sonar_backend.wsgi.application" 

121 

122 

123# Database 

124# https://docs.djangoproject.com/en/4.2/ref/settings/#databases 

125 

126# NOTE: The `env.db()` method is an alias for `db_url()`. 

127# We can use the code like `if not env.db()`. 

128# However, we need to set the `DATABASE_URL` in the environment; otherwise, 

129# we will get a message like the one below: 

130# UserWarning: Engine not recognized from the URL: {'NAME': '', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', 'ENGINE': ''} 

131# As of now, we just ignore the UserWarning message and still set it as a dictionary 

132# by using `env("DATABASE_URL")` this way. 

133if not os.environ.get("DATABASE_URL"): 

134 print("Using the default database.") 

135 database_connection = { 

136 "ENGINE": "django.db.backends.postgresql", 

137 "OPTIONS": {"options": "-c search_path=public"}, 

138 "NAME": env("POSTGRES_DB"), 

139 "USER": env("POSTGRES_USER"), 

140 "PASSWORD": env("POSTGRES_PASSWORD"), 

141 "HOST": env("POSTGRES_HOST"), 

142 "PORT": env("POSTGRES_PORT"), 

143 } 

144else: 

145 print("Using the provided database.") 

146 database_connection = env.db("DATABASE_URL") 

147 

148DATABASES = {"default": database_connection} 

149 

150 

151# Password validation 

152# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators 

153 

154AUTH_PASSWORD_VALIDATORS = [ 

155 { 

156 "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 

157 }, 

158 { 

159 "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 

160 }, 

161 { 

162 "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 

163 }, 

164 { 

165 "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 

166 }, 

167] 

168 

169 

170# Internationalization 

171# https://docs.djangoproject.com/en/4.2/topics/i18n/ 

172 

173LANGUAGE_CODE = "en-us" 

174 

175TIME_ZONE = "UTC" 

176 

177USE_I18N = True 

178 

179USE_TZ = True 

180 

181 

182# Static files (CSS, JavaScript, Images) 

183# https://docs.djangoproject.com/en/4.2/howto/static-files/ 

184 

185STATIC_URL = "static/" 

186 

187# Default primary key field type 

188# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field 

189 

190DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 

191 

192 

193DATA_UPLOAD_MAX_MEMORY_SIZE = None 

194 

195APSCHEDULER_RUN_NOW_TIMEOUT = 60 * 60 * 5 

196 

197# SONAR APP 

198PROFILE_IMPORT = env("PROFILE_IMPORT") 

199REDIS_URL = env("REDIS_URL") 

200 

201SAMPLE_BATCH_SIZE = env("SAMPLE_BATCH_SIZE") 

202PROPERTY_BATCH_SIZE = env("PROPERTY_BATCH_SIZE") 

203 

204SONAR_DATA_ENTRY_FOLDER = ( 

205 env("SONAR_DATA_ENTRY_FOLDER") 

206 if env("SONAR_DATA_ENTRY_FOLDER") 

207 else os.path.join(BASE_DIR, "import_data") 

208) 

209SONAR_DATA_PROCESSING_FOLDER = ( 

210 env("SONAR_DATA_PROCESSING_FOLDER") 

211 if env("SONAR_DATA_PROCESSING_FOLDER") 

212 else os.path.join(BASE_DIR, "processing_data") 

213) 

214SONAR_DATA_ARCHIVE = ( 

215 env("SONAR_DATA_ARCHIVE") 

216 if env("SONAR_DATA_ARCHIVE") 

217 else os.path.join(BASE_DIR, "archive_data") 

218) 

219 

220# Check if the directory already exists 

221if not os.path.exists(SONAR_DATA_ENTRY_FOLDER): 

222 os.makedirs(SONAR_DATA_ENTRY_FOLDER, exist_ok=True) 

223if not os.path.exists(SONAR_DATA_PROCESSING_FOLDER): 

224 os.makedirs(SONAR_DATA_PROCESSING_FOLDER, exist_ok=True) 

225if not os.path.exists(SONAR_DATA_ARCHIVE): 

226 os.makedirs(SONAR_DATA_ARCHIVE, exist_ok=True) 

227 

228# ------------------------------------------ 

229 

230LOGGER = logging.getLogger(__name__) 

231 

232LOG_PATH = env("LOG_PATH") 

233LOG_LEVEL = env("LOG_LEVEL") 

234if not os.path.exists(LOG_PATH): 

235 os.makedirs(LOG_PATH, exist_ok=True) 

236LOGGING = { 

237 "version": 1, 

238 "disable_existing_loggers": False, 

239 "formatters": { 

240 "json": { 

241 "()": CustomisedJSONFormatter, 

242 }, 

243 }, 

244 "handlers": { 

245 "console": { 

246 "class": "logging.StreamHandler", 

247 }, 

248 "app_log_file": { 

249 "level": LOG_LEVEL, 

250 "class": "logging.FileHandler", 

251 "filename": os.path.join( 

252 LOG_PATH, f"{datetime.today().strftime('%Y_%m_%d')}.log.json" 

253 ), 

254 "formatter": "json", 

255 }, 

256 }, 

257 "loggers": { 

258 "": { 

259 "level": LOG_LEVEL, 

260 "handlers": ["console", "app_log_file"], 

261 }, 

262 }, 

263} 

264 

265PERMISSION_RELEVANT_USER_GROUPS = ["admin", "read_only"] 

266 

267CACHES = { 

268 "default": { 

269 "BACKEND": "django_redis.cache.RedisCache", 

270 "LOCATION": f"{env('REDIS_URL')}1", 

271 "OPTIONS": { 

272 "CLIENT_CLASS": "django_redis.client.DefaultClient", 

273 }, 

274 } 

275} 

276CACHE_OBJECT_TTL = env.int( 

277 "CACHE_OBJECT_TTL", default=3600 

278) # (default: Time to Live 60 minutes) 

279 

280# If true, keep the data files that were sent from the CLI in an archive 

281# folder, even after that data has been imported into the database. 

282# Defaults to false, so the files are immediately deleted to save space. 

283KEEP_IMPORTED_DATA_FILES = env("KEEP_IMPORTED_DATA_FILES") 

284 

285# Celery settings 

286CELERY_BROKER_URL = f"{env('REDIS_URL')}0" 

287CELERY_RESULT_BACKEND = f"{env('REDIS_URL')}0" 

288 

289if DEBUG: 

290 INTERNAL_IPS = ("127.0.0.1",) 

291 INSTALLED_APPS += ["debug_toolbar"] 

292 MIDDLEWARE.insert(5, "debug_toolbar.middleware.DebugToolbarMiddleware") 

293 DEBUG_TOOLBAR_CONFIG = { 

294 "SHOW_TOOLBAR_CALLBACK": lambda request: True, 

295 } 

296 print(DATABASES)