settings.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. ##
  2. # Copyright (C) 2014 Jessica Tallon & Matt Molyneaux
  3. #
  4. # This file is part of Inboxen.
  5. #
  6. # Inboxen is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # Inboxen is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with Inboxen If not, see <http://www.gnu.org/licenses/>.
  18. ##
  19. from subprocess import Popen, PIPE
  20. import datetime
  21. import os
  22. import stat
  23. import warnings
  24. from django.contrib.messages import constants as message_constants
  25. from django.core import exceptions, urlresolvers
  26. from kombu.common import Broadcast, Exchange, Queue
  27. from kombu.serialization import registry
  28. import configobj
  29. import djcelery
  30. import jsondate
  31. import validate
  32. djcelery.setup_loader()
  33. ##
  34. # Most configuration can be done via settings.ini
  35. #
  36. # The file is searched for in the follow way:
  37. # 1. The environment variable "INBOXEN_CONFIG", which contains an absolute path
  38. # 2. ~/.config/inboxen/settings.ini
  39. # 3. settings.ini in the same folder as this file
  40. #
  41. # See inboxen/config_spec.ini for defaults, see below for comments
  42. ##
  43. # Shorthand for Django's default database backends
  44. db_dict = {
  45. "postgresql": "django.db.backends.postgresql_psycopg2",
  46. "mysql": "django.db.backends.mysql",
  47. "oracle": "django.db.backends.oracle",
  48. "sqlite": "django.db.backends.sqlite3",
  49. }
  50. # Shorthand for Django's default database backends
  51. cache_dict = {
  52. "database": "django.core.cache.backends.db.DatabaseCache",
  53. "dummy": "django.core.cache.backends.dummy.DummyCache",
  54. "file": "django.core.cache.backends.filebased.FileBasedCache",
  55. "localmem": "django.core.cache.backends.locmem.LocMemCache",
  56. "memcached": "django.core.cache.backends.memcached.PyLibMCCache",
  57. }
  58. is_testing = bool(int(os.getenv('INBOX_TESTING', '0')))
  59. BASE_DIR = os.path.dirname(__file__)
  60. if os.path.exists(os.getenv('INBOX_CONFIG', '')):
  61. CONFIG_PATH = os.getenv('INBOX_CONFIG')
  62. elif os.path.exists(os.path.expanduser("~/.config/inboxen/settings.ini")):
  63. CONFIG_PATH = os.path.expanduser("~/.config/inboxen/settings.ini")
  64. elif os.path.exists(os.path.join(BASE_DIR, "settings.ini")):
  65. CONFIG_PATH = os.path.join(BASE_DIR, "settings.ini")
  66. elif is_testing:
  67. CONFIG_PATH = ""
  68. else:
  69. raise exceptions.ImproperlyConfigured("You must provide a settings.ini file")
  70. # Check that our chosen settings file cannot be interacted with by other users
  71. try:
  72. mode = os.stat(CONFIG_PATH).st_mode
  73. except OSError:
  74. warnings.warn("Couldn't find settings.ini", ImportWarning)
  75. else:
  76. if mode & stat.S_IRWXO != 0:
  77. warnings.warn("Other users could be able to interact with your settings file. Please check file permissions on %s" % CONFIG_PATH)
  78. config_spec = os.path.join(BASE_DIR, "inboxen/config_spec.ini")
  79. config = configobj.ConfigObj(CONFIG_PATH, configspec=config_spec)
  80. config.validate(validate.Validator())
  81. # TODO: These could be merged into a custom validator
  82. try:
  83. SECRET_KEY = config["general"]["secret_key"]
  84. except KeyError:
  85. if is_testing:
  86. warnings.warn("You haven't set 'secret_key' in your settings.ini", ImportWarning)
  87. else:
  88. raise exceptions.ImproperlyConfigured("You must set 'secret_key' in your settings.ini")
  89. if len(config["general"]["admin_names"]) != len(config["general"]["admin_emails"]):
  90. raise exceptions.ImproperlyConfigured("You must have the same number of admin_names as admin_emails settings.ini")
  91. # Admins (and managers)
  92. ADMINS = zip(config["general"]["admin_names"], config["general"]["admin_emails"])
  93. # List of hosts allowed
  94. ALLOWED_HOSTS = config["general"]["allowed_hosts"]
  95. # Enable debugging - DO NOT USE IN PRODUCTION
  96. DEBUG = config["general"]["debug"]
  97. # Alloew new users to register
  98. ENABLE_REGISTRATION = config["general"]["enable_registration"]
  99. # Cooloff time, in minutes, for failed logins
  100. LOGIN_ATTEMPT_COOLOFF = config["general"]["login_attempt_cooloff"]
  101. # Maximum number of unsuccessful login attempts
  102. LOGIN_ATTEMPT_LIMIT = config["general"]["login_attempt_limit"]
  103. # Language code, e.g. en-gb
  104. LANGUAGE_CODE = config["general"]["language_code"]
  105. # Where `manage.py collectstatic` puts static files
  106. STATIC_ROOT = os.path.join(BASE_DIR, config["general"]["static_root"])
  107. # Email the server uses when sending emails
  108. SERVER_EMAIL = config["general"]["server_email"]
  109. # Site name used in page titles
  110. SITE_NAME = config["general"]["site_name"]
  111. # Link to source code
  112. SOURCE_LINK = config["general"]["source_link"]
  113. # Time zone
  114. TIME_ZONE = config["general"]["time_zone"]
  115. # Length of the local part (bit before the @) of autogenerated inbox addresses
  116. INBOX_LENGTH = config["inbox"]["inbox_length"]
  117. # Maximum number of free inboxes before a request for more will be generated
  118. MIN_INBOX_FOR_REQUEST = config["inbox"]["min_inbox_for_request"]
  119. # Increase the pool amount by this number when a user request is granted
  120. REQUEST_NUMBER = config["inbox"]["request_number"]
  121. # Where Celery looks for new tasks and stores results
  122. BROKER_URL = config["tasks"]["broker_url"]
  123. # Number of Celery processes to start
  124. CELERYD_CONCURRENCY = config["tasks"]["concurrency"]
  125. # Path where liberation data is temporarily stored
  126. LIBERATION_PATH = os.path.join(BASE_DIR, config["tasks"]["liberation"]["path"])
  127. # Databases!
  128. DATABASES = {
  129. 'default': {
  130. 'ENGINE': db_dict[config["database"]["engine"]],
  131. 'USER': config["database"]["user"],
  132. 'PASSWORD': config["database"]["password"],
  133. 'HOST': config["database"]["host"],
  134. 'PORT': config["database"]["port"],
  135. }
  136. }
  137. # "name" is a path for sqlite databases
  138. if config["database"]["engine"] == "sqlite":
  139. DATABASES["default"]["NAME"] = os.path.join(BASE_DIR, config["database"]["name"])
  140. else:
  141. DATABASES["default"]["NAME"] = config["database"]["name"]
  142. # Caches!
  143. CACHES = {
  144. 'default': {
  145. 'BACKEND': cache_dict[config["cache"]["backend"]],
  146. 'TIMEOUT': config["cache"]["timeout"],
  147. }
  148. }
  149. if config["cache"]["backend"] == "file":
  150. if config["cache"]["location"] == "":
  151. # sane default for minimum configuration
  152. CACHES["default"]["LOCATION"] = os.path.join(BASE_DIR, "inboxen_cache")
  153. else:
  154. CACHES["default"]["LOCATION"] = os.path.join(BASE_DIR, config["cache"]["location"])
  155. else:
  156. CACHES["default"]["LOCATION"] = config["cache"]["location"]
  157. # Hash used to store uniqueness of certain models
  158. # if you change this, you'll need to do a datamigration to change the rest
  159. COLUMN_HASHER = "sha1"
  160. ##
  161. # To override the following settings, create a separate settings module.
  162. # Import this module, override what you need to and set the environment
  163. # variable DJANGO_SETTINGS_MODULE to your module. See Django docs for details
  164. ##
  165. if not DEBUG:
  166. # These security settings are annoying while debugging
  167. CSRF_COOKIE_SECURE = True
  168. SESSION_COOKIE_SECURE = True
  169. ##
  170. # Celery options
  171. ##
  172. # load custom kombu encoder
  173. registry.register('json_date', jsondate.dumps, jsondate.loads,
  174. content_type='application/json+date', content_encoding='utf-8')
  175. CELERY_SEND_TASK_ERROR_EMAILS = True
  176. CELERY_RESULT_BACKEND = BROKER_URL
  177. CELERY_ACCEPT_CONTENT = ['json', 'json_date']
  178. CELERY_TASK_SERIALIZER = "json_date"
  179. CELERY_RESULT_SERIALIZER = "json_date"
  180. CELERY_QUEUES = (
  181. Queue('default', Exchange('default'), routing_key='default'),
  182. Broadcast('broadcast_tasks'),
  183. )
  184. CELERY_ROUTES = {'queue.tasks.force_garbage_collection': {'queue': 'broadcast_tasks'}}
  185. CELERY_DEFAULT_QUEUE = 'default'
  186. CELERY_DEFAULT_EXCHANGE = 'default'
  187. CELERY_DEFAULT_ROUTING_KEY = 'default'
  188. CELERYBEAT_SCHEDULE = {
  189. 'statistics': {
  190. 'task': 'queue.tasks.statistics',
  191. 'schedule': datetime.timedelta(days=1),
  192. },
  193. 'cleanup': {
  194. 'task': 'queue.delete.tasks.clean_orphan_models',
  195. 'schedule': datetime.timedelta(days=1),
  196. },
  197. 'requests': {
  198. 'task': 'queue.tasks.requests',
  199. 'schedule': datetime.timedelta(days=1),
  200. },
  201. }
  202. ##
  203. # Django options
  204. ##
  205. MESSAGE_TAGS = {message_constants.ERROR: 'danger'}
  206. SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
  207. TEMPLATE_DEBUG = DEBUG
  208. TEST_RUNNER = 'djcelery.contrib.test_runner.CeleryTestSuiteRunner'
  209. TWO_FACTOR_PATCH_ADMIN = False
  210. USE_I18N = True
  211. USE_L10N = True
  212. USE_TZ = True
  213. STATIC_URL = '/static/'
  214. STATICFILES_FINDERS = (
  215. 'django.contrib.staticfiles.finders.FileSystemFinder',
  216. 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
  217. )
  218. STATICFILES_STORAGE = 'inboxen.storage.ManifestStaticFilesStorage'
  219. AUTHENTICATION_BACKENDS = (
  220. 'website.backends.RateLimitWithSettings',
  221. )
  222. # Make sure all custom template tags are thread safe
  223. # https://docs.djangoproject.com/en/1.6/howto/custom-template-tags/#template-tag-thread-safety
  224. TEMPLATE_LOADERS = (
  225. ('django.template.loaders.cached.Loader', (
  226. 'django.template.loaders.filesystem.Loader',
  227. 'django.template.loaders.app_directories.Loader',
  228. )),
  229. )
  230. TEMPLATE_CONTEXT_PROCESSORS = (
  231. "django.contrib.auth.context_processors.auth",
  232. "django.core.context_processors.debug",
  233. "django.core.context_processors.i18n",
  234. "django.core.context_processors.static",
  235. "django.core.context_processors.tz",
  236. "django.core.context_processors.request",
  237. "django.contrib.messages.context_processors.messages",
  238. "website.context_processors.reduced_settings_context"
  239. )
  240. MIDDLEWARE_CLASSES = (
  241. 'django.middleware.common.CommonMiddleware',
  242. 'django.contrib.sessions.middleware.SessionMiddleware',
  243. 'django.middleware.csrf.CsrfViewMiddleware',
  244. 'django.middleware.locale.LocaleMiddleware',
  245. 'django.contrib.auth.middleware.AuthenticationMiddleware',
  246. 'django_otp.middleware.OTPMiddleware',
  247. 'django.contrib.messages.middleware.MessageMiddleware',
  248. 'async_messages.middleware.AsyncMiddleware',
  249. 'website.middleware.RateLimitMiddleware',
  250. 'django.middleware.clickjacking.XFrameOptionsMiddleware',
  251. )
  252. INSTALLED_APPS = (
  253. 'django.contrib.auth',
  254. 'django.contrib.contenttypes',
  255. 'django.contrib.sessions',
  256. 'django.contrib.messages',
  257. 'django.contrib.staticfiles',
  258. 'django.contrib.admin',
  259. 'bootstrapform',
  260. 'south',
  261. 'django_extensions',
  262. 'watson',
  263. 'djcelery',
  264. 'django_otp',
  265. 'django_otp.plugins.otp_static',
  266. 'django_otp.plugins.otp_totp',
  267. 'two_factor',
  268. 'inboxen',
  269. 'blog',
  270. 'website',
  271. 'queue',
  272. 'queue.delete',
  273. 'queue.liberate',
  274. 'tickets',
  275. 'termsofservice',
  276. )
  277. if DEBUG:
  278. INSTALLED_APPS += ('debug_toolbar',)
  279. ROOT_URLCONF = 'website.urls'
  280. LOGIN_URL = urlresolvers.reverse_lazy("user-login")
  281. LOGOUT_URL = urlresolvers.reverse_lazy("user-logout")
  282. LOGIN_REDIRECT_URL = urlresolvers.reverse_lazy("user-home")
  283. # Python dotted path to the WSGI application used by Django's runserver.
  284. WSGI_APPLICATION = 'website.wsgi.application'
  285. ##
  286. # Salmon. Splash.
  287. ##
  288. SALMON_SERVER = {"host": "localhost", "port": 8823, "type": "smtp"}
  289. ##
  290. # Misc.
  291. ##
  292. try:
  293. process = Popen("git rev-parse HEAD".split(), stdout=PIPE, close_fds=True, cwd=BASE_DIR)
  294. output = process.communicate()[0].strip()
  295. if not process.returncode:
  296. os.environ["INBOXEN_COMMIT_ID"] = output
  297. else:
  298. os.environ["INBOXEN_COMMIT_ID"] = "UNKNOWN"
  299. except OSError, TypeError:
  300. os.environ["INBOXEN_COMMIT_ID"] = "UNKNOWN"