igs-slm 0.1.2b0__py3-none-any.whl → 0.1.5b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. igs_slm-0.1.5b0.dist-info/METADATA +115 -0
  2. {igs_slm-0.1.2b0.dist-info → igs_slm-0.1.5b0.dist-info}/RECORD +192 -172
  3. {igs_slm-0.1.2b0.dist-info → igs_slm-0.1.5b0.dist-info}/WHEEL +1 -1
  4. igs_slm-0.1.5b0.dist-info/entry_points.txt +3 -0
  5. {igs_slm-0.1.2b0.dist-info → igs_slm-0.1.5b0.dist-info/licenses}/LICENSE +1 -1
  6. slm/__init__.py +17 -14
  7. slm/admin.py +32 -5
  8. slm/api/edit/views.py +22 -9
  9. slm/api/public/views.py +10 -8
  10. slm/api/views.py +45 -6
  11. slm/apps.py +28 -6
  12. slm/authentication.py +3 -2
  13. slm/bin/startproject.py +102 -31
  14. slm/bin/templates/{{ project_dir }}/pyproject.toml +30 -21
  15. slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/base.py +12 -1
  16. slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/develop/__init__.py +5 -27
  17. slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/production/__init__.py +6 -27
  18. slm/bin/templates/{{ project_dir }}/src/sites/{{ site }}/validation.py +29 -0
  19. slm/context.py +5 -0
  20. slm/defines/AlertLevel.py +10 -2
  21. slm/defines/AntennaCalibration.py +6 -4
  22. slm/defines/AntennaFeatures.py +12 -9
  23. slm/defines/AntennaReferencePoint.py +12 -10
  24. slm/defines/Aspiration.py +4 -2
  25. slm/defines/CardinalDirection.py +6 -4
  26. slm/defines/CollocationStatus.py +3 -1
  27. slm/defines/EquipmentState.py +6 -12
  28. slm/defines/FlagSeverity.py +4 -2
  29. slm/defines/FractureSpacing.py +7 -5
  30. slm/defines/FrequencyStandardType.py +6 -4
  31. slm/defines/GeodesyMLVersion.py +2 -0
  32. slm/defines/Instrumentation.py +9 -7
  33. slm/defines/LogEntryType.py +17 -15
  34. slm/defines/SLMFileType.py +4 -2
  35. slm/defines/SiteFileUploadStatus.py +7 -24
  36. slm/defines/SiteLogFormat.py +8 -32
  37. slm/defines/SiteLogStatus.py +9 -28
  38. slm/defines/TectonicPlates.py +18 -16
  39. slm/manage.py +24 -0
  40. slm/management/commands/check_upgrade.py +142 -0
  41. slm/management/commands/generate_sinex.py +110 -92
  42. slm/management/commands/head_from_index.py +11 -8
  43. slm/management/commands/import_archive.py +27 -18
  44. slm/management/commands/import_equipment.py +1 -1
  45. slm/management/commands/sitelog.py +1 -3
  46. slm/management/commands/synchronize.py +1 -1
  47. slm/management/commands/validate_db.py +6 -4
  48. slm/map/defines.py +18 -14
  49. slm/map/templates/slm/map.html +4 -6
  50. slm/migrations/0001_remove_archiveindex_no_overlapping_ranges_per_site_and_more.py +26 -0
  51. slm/migrations/0002_alter_archivedsitelog_file_and_more.py +44 -0
  52. slm/migrations/0003_alter_archivedsitelog_name_and_more.py +35 -0
  53. slm/migrations/0004_alter_site_name.py +24 -0
  54. slm/migrations/0005_slmversion.py +30 -0
  55. slm/migrations/0017_alter_logentry_unique_together_and_more.py +3 -1
  56. slm/migrations/0018_afix_deleted.py +3 -1
  57. slm/migrations/0018_alter_siteantenna_options_and_more.py +87 -56
  58. slm/migrations/0019_remove_siteantenna_marker_enu_siteantenna_marker_une_and_more.py +1 -1
  59. slm/migrations/0023_archivedsitelog_gml_version_and_more.py +1 -1
  60. slm/migrations/0031_alter_antenna_features.py +44 -0
  61. slm/migrations/0032_archiveindex_valid_range_and_more.py +84 -0
  62. slm/migrations/add_index_order_index.py +54 -0
  63. slm/migrations/normalize_index.py +147 -0
  64. slm/migrations/simplify_index.py +48 -0
  65. slm/migrations/verify_index.py +67 -0
  66. slm/models/__init__.py +2 -0
  67. slm/models/alerts.py +7 -10
  68. slm/models/data.py +1 -2
  69. slm/models/equipment.py +1 -1
  70. slm/models/fields.py +41 -0
  71. slm/models/index.py +183 -53
  72. slm/models/sitelog.py +35 -38
  73. slm/models/system.py +72 -31
  74. slm/models/user.py +1 -1
  75. slm/parsing/__init__.py +34 -17
  76. slm/parsing/legacy/binding.py +65 -34
  77. slm/parsing/legacy/parser.py +2 -2
  78. slm/parsing/xsd/binding.py +1 -1
  79. slm/parsing/xsd/parser.py +1 -2
  80. slm/receivers/__init__.py +2 -2
  81. slm/receivers/index.py +2 -1
  82. slm/receivers/migration.py +21 -0
  83. slm/settings/__init__.py +192 -4
  84. slm/settings/assets.py +26 -0
  85. slm/settings/auth.py +18 -14
  86. slm/settings/ckeditor.py +12 -6
  87. slm/settings/debug.py +2 -2
  88. slm/settings/emails.py +50 -0
  89. slm/settings/internationalization.py +8 -6
  90. slm/settings/logging.py +100 -88
  91. slm/settings/platform/darwin.py +16 -6
  92. slm/settings/rest.py +20 -15
  93. slm/settings/root.py +192 -98
  94. slm/settings/routines.py +5 -1
  95. slm/settings/secrets.py +20 -31
  96. slm/settings/security.py +7 -5
  97. slm/settings/slm.py +35 -16
  98. slm/settings/static_templates.py +12 -9
  99. slm/settings/templates.py +31 -25
  100. slm/settings/uploads.py +33 -5
  101. slm/settings/urls.py +1 -1
  102. slm/settings/validation.py +165 -165
  103. slm/signals.py +3 -2
  104. slm/static/slm/css/style.css +37 -36
  105. slm/static/slm/js/autocomplete.js +6 -4
  106. slm/static/slm/js/file_modal.js +62 -0
  107. slm/static/slm/js/form.js +3 -3
  108. slm/static/slm/js/formWidget.js +3 -3
  109. slm/static/slm/js/persistable.js +5 -1
  110. slm/templates/admin/base.html +1 -0
  111. slm/templates/rest_framework/base.html +23 -11
  112. slm/templates/slm/base.html +27 -22
  113. slm/templates/slm/forms/widgets/auto_complete.html +12 -11
  114. slm/templates/slm/forms/widgets/auto_complete_multiple.html +8 -7
  115. slm/templates/slm/station/download.html +6 -6
  116. slm/templates/slm/station/edit.html +9 -17
  117. slm/templates/slm/station/review.html +5 -3
  118. slm/templates/slm/station/upload.html +4 -1
  119. slm/templates/slm/station/uploads/legacy.html +1 -1
  120. slm/templates/slm/widgets/alert_scroll.html +4 -8
  121. slm/templates/slm/widgets/filelist.html +0 -5
  122. slm/templates/slm/widgets/log_scroll.html +2 -13
  123. slm/templates/slm/widgets/stationlist.html +2 -8
  124. slm/templatetags/slm.py +70 -9
  125. slm/utils.py +13 -4
  126. slm/validators.py +14 -14
  127. slm/views.py +6 -6
  128. slm/wsgi.py +16 -0
  129. igs_slm-0.1.2b0.dist-info/METADATA +0 -151
  130. igs_slm-0.1.2b0.dist-info/entry_points.txt +0 -3
  131. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/validation.py +0 -11
  132. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/__init__.py +0 -0
  133. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/__init__.py +0 -0
  134. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/develop/local.py +0 -0
  135. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/develop/wsgi.py +0 -0
  136. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/manage.py +0 -0
  137. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/production/wsgi.py +0 -0
  138. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/urls.py +0 -0
  139. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/__init__.py +0 -0
  140. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/admin.py +0 -0
  141. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/apps.py +0 -0
  142. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/management/__init__.py +0 -0
  143. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/management/commands/__init__.py +0 -0
  144. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/management/commands/import_archive.py +0 -0
  145. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/migrations/__init__.py +0 -0
  146. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/models.py +0 -0
  147. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/templates/slm/base.html +0 -0
  148. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/urls.py +0 -0
  149. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/views.py +0 -0
slm/settings/__init__.py CHANGED
@@ -1,13 +1,125 @@
1
+ """
2
+ These settings utilities help us compose settings across multiple files.
3
+
4
+ .. warning::
5
+
6
+ These functions only work when called from a settings file because
7
+ they operate on the calling scope. Do not call these functions from
8
+ any other location.
9
+ """
10
+
11
+ import re
1
12
  import sys
13
+ import typing as t
14
+ from functools import partial
15
+ from os import PathLike
16
+ from pathlib import Path
2
17
 
18
+ import environ
3
19
  import importlib_resources
20
+ from django.core.exceptions import ImproperlyConfigured
21
+
22
+ re.Pattern
4
23
 
5
24
 
6
25
  class _NotGiven:
7
26
  pass
8
27
 
9
28
 
10
- def set_default(var_name, default, set_if_none=True):
29
+ _environ: t.Optional[environ.FileAwareEnv] = None
30
+ _base_dir: t.Optional[Path] = None
31
+ _email_rgx: t.Optional[re.Pattern] = None
32
+
33
+
34
+ def split_email_str(email: str) -> t.Tuple[str, str]:
35
+ """
36
+ Split an email string into display name and email address.
37
+
38
+ If there is no display name it will be the empty string.
39
+
40
+ :param email: An email string. E.g.:
41
+ * ``name@example.com``
42
+ * ``Last, First <name@example.com>``
43
+ * ``"First Last" <name@example.com>``
44
+
45
+ :return: A 2-tuple of (display name, email address)
46
+ """
47
+ global _email_rgx
48
+ if "<" not in email:
49
+ return ("", email)
50
+ if not _email_rgx:
51
+ _email_rgx = re.compile(r"\s+[\"']?(?P<display>.*)[\"']?\s+<(?P<email>.*)>")
52
+ if not (match := _email_rgx.search(email)):
53
+ raise ImproperlyConfigured(f"{email} is not a valid email string.")
54
+ return match.groupdict["display"].strip(), match.groupdict["email"].strip()
55
+
56
+
57
+ def _is_relative_to(parent: Path, child: Path) -> bool:
58
+ # TODO remove when python >=3.9
59
+ if sys.version_info[:2] < (3, 9):
60
+ try:
61
+ child.relative_to(parent)
62
+ return True
63
+ except ValueError:
64
+ return False
65
+ return child.is_relative_to(parent)
66
+
67
+
68
+ def env(**kwargs) -> environ.FileAwareEnv:
69
+ """
70
+ Fetch the :class:`environ.Env` object that should be used to configure settings.
71
+ If an environment file exists it will be read in. The environment file precendence
72
+ is:
73
+
74
+ 1. The path in the environment variable SLM_ENV
75
+ 2. BASE_DIR / .env
76
+
77
+ :return: The :class:`~environ.FileAwareEnv` object configured to read our
78
+ environment variables and files.
79
+ """
80
+ global _environ
81
+ if _environ is None:
82
+ scope = sys._getframe(1).f_globals
83
+ default_env = scope.get("BASE_DIR", "")
84
+ if default_env:
85
+ default_env = str(Path(default_env) / ".env")
86
+
87
+ DEBUG = kwargs.pop("DEBUG", (bool, False))
88
+ _environ = environ.FileAwareEnv(DEBUG=DEBUG, **kwargs)
89
+ override_env = _environ.str("SLM_ENV", scope.get("SLM_ENV", default_env))
90
+ if override_env and Path(override_env).is_file():
91
+ _environ.read_env(str(override_env))
92
+ return _environ
93
+
94
+
95
+ def base_dir(scope=None) -> Path:
96
+ """
97
+ Fetch the :setting:`BASE_DIR`, this will raise an
98
+ :exc:`~django.core.exceptions.ImproperlyConfigured` exception if it does not exist yet.
99
+
100
+ :param scope: The settings scope (do not pass if calling from a settings file)
101
+ :return: The :setting:`BASE_DIR` as a :class:`pathlib.Path` object.
102
+ :raises: :exc:`~django.core.exceptions.ImproperlyConfigured` if :setting:`BASE_DIR` is not
103
+ defined.
104
+ """
105
+ global _base_dir
106
+ if not _base_dir:
107
+ scope = scope or sys._getframe(1).f_globals
108
+ _base_dir = scope.get("BASE_DIR", None)
109
+ if _base_dir is None:
110
+ raise ImproperlyConfigured("BASE_DIR was needed before it was defined!")
111
+ _base_dir = Path(_base_dir)
112
+ if not _base_dir.is_dir():
113
+ if scope.get("DEBUG", False):
114
+ _base_dir.mkdir(mode=0o775, parents=True, exist_ok=True)
115
+ else:
116
+ raise ImproperlyConfigured(
117
+ f"BASE_DIR: {_base_dir} is not a directory that exists."
118
+ )
119
+ return _base_dir
120
+
121
+
122
+ def set_default(var_name: str, default: t.Any, set_if_none: bool = True) -> t.Any:
11
123
  """
12
124
  Set the value of the given variable in the calling scope to a default value if it has been not been previously
13
125
  defined.
@@ -23,7 +135,7 @@ def set_default(var_name, default, set_if_none=True):
23
135
  return scope[var_name]
24
136
 
25
137
 
26
- def is_defined(var_name):
138
+ def is_defined(var_name: str) -> bool:
27
139
  """
28
140
  Returns true if the given variable has been defined in the calling scope.
29
141
 
@@ -33,7 +145,7 @@ def is_defined(var_name):
33
145
  return var_name in sys._getframe(1).f_globals
34
146
 
35
147
 
36
- def get_setting(var_name, default=_NotGiven):
148
+ def get_setting(var_name: str, default: t.Any = _NotGiven) -> t.Any:
37
149
  """
38
150
  Returns the value of the setting if it exists, if it does not exist the value
39
151
  given to default is returned. If no default value is given and the setting
@@ -49,7 +161,83 @@ def get_setting(var_name, default=_NotGiven):
49
161
  return value
50
162
 
51
163
 
52
- def resource(package, file):
164
+ def slm_path(
165
+ value: str, must_exist: bool = False, mk_dirs: bool = False
166
+ ) -> t.Optional[str]:
167
+ """
168
+ Resolve a configuration path. If the path is relative it will be resolved under
169
+ :setting:`BASE_DIR`. If the value is falsey it will be returned as-is even if
170
+ ``must_exist`` is set.
171
+
172
+ :param value: The path as a string
173
+ :param must_exist: If True, raise an
174
+ :exc:`~django.core.exceptions.ImproperlyConfigured` if the path does not exist.
175
+ :param mk_dirs: If True and the path is relative to :setting:`BASE_DIR` create the
176
+ path as a directory if it does not already exist.
177
+ :raises: :exc:`~django.core.exceptions.ImproperlyConfigured` if :setting:`BASE_DIR` is
178
+ needed but not defined, or does not exist or if ``must_exist`` is ``True`` but the
179
+ path does not exist.
180
+ """
181
+ if not value:
182
+ return value
183
+ pth = Path(str(value))
184
+
185
+ if relative := not pth.is_absolute():
186
+ pth = base_dir(sys._getframe(1).f_globals) / pth
187
+
188
+ if not pth.exists() and mk_dirs:
189
+ relative = relative or _is_relative_to(
190
+ base_dir(sys._getframe(1).f_globals), pth
191
+ )
192
+ if relative:
193
+ pth.mkdir(
194
+ mode=0o775 if sys._getframe(1).f_globals.get("DEBUG", False) else 0o770,
195
+ parents=True,
196
+ exist_ok=True,
197
+ )
198
+
199
+ if must_exist and not pth.exists():
200
+ raise ImproperlyConfigured(f"{pth} does not exist!")
201
+ return str(pth)
202
+
203
+
204
+ slm_path_must_exist = partial(slm_path, must_exist=True)
205
+ """
206
+ A shortcut for :func:`~slm.settings.slm_path` with ``must_exist=True``.
207
+ """
208
+
209
+ slm_path_mk_dirs_must_exist = partial(slm_path, must_exist=True, mk_dirs=True)
210
+ """
211
+ A shortcut for :func:`~slm.settings.slm_path` with ``must_exist=True`` and
212
+ ``mk_dirs=True``.
213
+ """
214
+
215
+
216
+ def unset(var_name: str):
217
+ """
218
+ Unset the value of the given variable in the calling scope.
219
+
220
+ :param var_name: The name of the variable to unset in the calling scope.
221
+ """
222
+ scope = sys._getframe(1).f_globals
223
+ if var_name in scope:
224
+ del scope[var_name]
225
+
226
+
227
+ def resource(package: str, file: PathLike):
228
+ """
229
+ Use a packaged resource file as a settings file include. If you are
230
+ including a settings file from another package you should pass this to
231
+ split_settings.tools.include:
232
+
233
+ .. code-block:: python
234
+
235
+ include(resource("slm.settings", "root.py"))
236
+
237
+ :param package: The string import path of the package containing the module
238
+ :param file: The name of the python settings module
239
+ :return: The full string path to the resource.
240
+ """
53
241
  profile2 = importlib_resources.files(package) / file
54
242
  with importlib_resources.as_file(profile2) as profile2_path:
55
243
  return str(profile2_path)
slm/settings/assets.py ADDED
@@ -0,0 +1,26 @@
1
+ """
2
+ Defines settings that control how assets are compressed and bundled using django-compressor.
3
+
4
+ https://django-compressor.readthedocs.io
5
+ """
6
+
7
+ from slm.settings import get_setting, set_default
8
+
9
+ set_default("COMPRESS_OFFLINE", True)
10
+ set_default("COMPRESS_ROOT", get_setting("STATIC_ROOT"))
11
+ set_default("COMPRESS_URL", get_setting("STATIC_URL"))
12
+ set_default("COMPRESS_OUTPUT_DIR", "assets")
13
+
14
+ set_default(
15
+ "COMPRESS_PRECOMPILERS",
16
+ (
17
+ (
18
+ "text/javascript",
19
+ "npx -yes esbuild@latest {infile} --bundle --minify --outfile={outfile}",
20
+ ),
21
+ (
22
+ "module",
23
+ "npx -yes esbuild@latest {infile} --bundle --minify --outfile={outfile}",
24
+ ),
25
+ ),
26
+ )
slm/settings/auth.py CHANGED
@@ -1,15 +1,19 @@
1
- ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
2
- ACCOUNT_UNIQUE_EMAIL = True
3
- ACCOUNT_USER_MODEL_USERNAME_FIELD = None
4
- ACCOUNT_EMAIL_REQUIRED = True
5
- ACCOUNT_USERNAME_REQUIRED = False
6
- ACCOUNT_AUTHENTICATION_METHOD = "email"
7
- LOGIN_URL = "/accounts/login/"
8
- LOGIN_REDIRECT_URL = "/"
1
+ from slm.settings import set_default
9
2
 
10
- AUTHENTICATION_BACKENDS = [
11
- # Needed to login by username in Django admin, regardless of `allauth`
12
- "django.contrib.auth.backends.ModelBackend",
13
- # `allauth` specific authentication methods, such as login by e-mail
14
- "allauth.account.auth_backends.AuthenticationBackend",
15
- ]
3
+ set_default("ACCOUNT_LOGIN_ON_PASSWORD_RESET", True)
4
+ set_default("ACCOUNT_UNIQUE_EMAIL", True)
5
+ set_default("ACCOUNT_USER_MODEL_USERNAME_FIELD", None)
6
+ set_default("ACCOUNT_LOGIN_METHODS", {"email"})
7
+ set_default("ACCOUNT_SIGNUP_FIELDS", ["email*", "password1*", "password2*"])
8
+ set_default("LOGIN_URL", "/accounts/login/")
9
+ set_default("LOGIN_REDIRECT_URL", "/")
10
+
11
+ set_default(
12
+ "AUTHENTICATION_BACKENDS",
13
+ [
14
+ # Needed to login by username in Django admin, regardless of `allauth`
15
+ "django.contrib.auth.backends.ModelBackend",
16
+ # `allauth` specific authentication methods, such as login by e-mail
17
+ "allauth.account.auth_backends.AuthenticationBackend",
18
+ ],
19
+ )
slm/settings/ckeditor.py CHANGED
@@ -2,13 +2,19 @@ from slm.settings import get_setting, set_default
2
2
 
3
3
  # your webserver should be configured to bypass Django and server
4
4
  # MEDIA_ROOT / CKEDITOR_UPLOAD_PATH directly
5
- CKEDITOR_UPLOAD_PATH = get_setting("MEDIA_ROOT") / "public/"
5
+ set_default("CKEDITOR_UPLOAD_PATH", get_setting("MEDIA_ROOT") / "public/")
6
6
 
7
- CKEDITOR_CONFIGS = {
8
- "richtextinput": {
9
- "width": "100%",
7
+ set_default(
8
+ "CKEDITOR_CONFIGS",
9
+ {
10
+ "richtextinput": {
11
+ "width": "100%",
12
+ },
13
+ "default": {
14
+ # exportpdf is not included but still getting the api key warning in js console??
15
+ "removePlugins": "exportpdf",
16
+ },
10
17
  },
11
- "default": {},
12
- }
18
+ )
13
19
 
14
20
  set_default("SILENCED_SYSTEM_CHECKS", []).append("ckeditor.W001")
slm/settings/debug.py CHANGED
@@ -4,10 +4,10 @@ from django.core.exceptions import ImproperlyConfigured
4
4
 
5
5
  from slm.settings import get_setting, set_default
6
6
 
7
- if get_setting("DJANGO_DEBUG_TOOLBAR", None):
7
+ if get_setting("SLM_DEBUG_TOOLBAR", False):
8
8
  if not find_spec("debug_toolbar"):
9
9
  raise ImproperlyConfigured(
10
- "DJANGO_DEBUG_TOOLBAR is True, but django-debug-toolbar is not" "installed."
10
+ "SLM_DEBUG_TOOLBAR is True, but django-debug-toolbar is notinstalled."
11
11
  )
12
12
 
13
13
  # list any middlewares that might be encountered on the stack that must
slm/settings/emails.py ADDED
@@ -0,0 +1,50 @@
1
+ from slm.settings import env as settings_environment
2
+ from slm.settings import (
3
+ get_setting,
4
+ slm_path_mk_dirs_must_exist,
5
+ slm_path_must_exist,
6
+ split_email_str,
7
+ )
8
+
9
+ env = settings_environment()
10
+
11
+ _email_config = env.email_url(
12
+ "SLM_EMAIL_SERVER", default=get_setting("SLM_EMAIL_SERVER", "smtp://localhost:25")
13
+ )
14
+ if EMAIL_FILE_PATH := _email_config.pop("EMAIL_FILE_PATH", None):
15
+ EMAIL_FILE_PATH = slm_path_mk_dirs_must_exist(EMAIL_FILE_PATH)
16
+
17
+ if not EMAIL_FILE_PATH:
18
+ del EMAIL_FILE_PATH
19
+
20
+ vars().update(_email_config)
21
+
22
+ SERVER_EMAIL = env("SERVER_EMAIL", default=get_setting("SERVER_EMAIL", None))
23
+ if SERVER_EMAIL is None:
24
+ domain = (
25
+ get_setting("SLM_SITE_NAME", None)
26
+ or get_setting("ALLOWED_HOSTS", ["localhost"])[0]
27
+ )
28
+ SERVER_EMAIL = f"noreply@{domain}"
29
+
30
+ DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL", default=SERVER_EMAIL)
31
+
32
+
33
+ EMAIL_SSL_CERTFILE = env(
34
+ "EMAIL_SSL_CERTFILE",
35
+ slm_path_must_exist,
36
+ default=get_setting("EMAIL_SSL_CERTFILE", None),
37
+ )
38
+ EMAIL_SSL_KEYFILE = env(
39
+ "EMAIL_SSL_KEYFILE",
40
+ slm_path_must_exist,
41
+ default=get_setting("EMAIL_SSL_KEYFILE", None),
42
+ )
43
+
44
+
45
+ # these emails will receive email reports when 500 HTTP messages are returned
46
+ # TODO - on Django 6+ change this from email_str -> tuple to tuple -> email str
47
+ ADMINS = [
48
+ split_email_str(email) if isinstance(email, str) else email
49
+ for email in env.list("ADMINS", default=get_setting("ADMINS", []))
50
+ ]
@@ -1,12 +1,14 @@
1
1
  # Internationalization
2
2
  # https://docs.djangoproject.com/en/stable/topics/i18n/
3
+ from slm.settings import env as settings_environment
4
+ from slm.settings import get_setting, set_default
3
5
 
4
- LANGUAGE_CODE = "en"
6
+ env = settings_environment()
7
+
8
+ LANGUAGE_CODE = env("LANGUAGE_CODE", default=get_setting("LANGUAGE_CODE", "en-us"))
5
9
 
6
10
  # ALL TIMES IN THE DB SHOULD BE STORED IN UTC - The web interface can then
7
11
  # reliably translate this into browser local time
8
- TIME_ZONE = "UTC"
9
-
10
- USE_I18N = False
11
-
12
- USE_TZ = True
12
+ set_default("TIME_ZONE", "UTC")
13
+ set_default("USE_I18N", not bool(LANGUAGE_CODE.startswith("en")))
14
+ set_default("USE_TZ", True)
slm/settings/logging.py CHANGED
@@ -1,11 +1,20 @@
1
1
  import os
2
2
  from logging import Filter
3
3
 
4
- from slm.settings import get_setting
4
+ from slm.settings import env as settings_environment
5
+ from slm.settings import get_setting, is_defined, slm_path_mk_dirs_must_exist
6
+
7
+ env = settings_environment()
5
8
 
6
9
  DEBUG = get_setting("DEBUG", False)
7
- DEFAULT_LOG_LEVEL = "DEBUG" if DEBUG else "INFO"
8
- LOG_DIR = get_setting("LOG_DIR", get_setting("SITE_DIR") / "logs")
10
+ SLM_LOG_LEVEL = env(
11
+ "SLM_LOG_LEVEL", default=get_setting("SLM_LOG_LEVEL", "DEBUG" if DEBUG else "INFO")
12
+ )
13
+ SLM_LOG_DIR = env(
14
+ "SLM_LOG_DIR",
15
+ slm_path_mk_dirs_must_exist,
16
+ default=get_setting("SLM_LOG_DIR", get_setting("BASE_DIR") / "logs"),
17
+ )
9
18
  SLM_MANAGEMENT_MODE = get_setting("SLM_MANAGEMENT_MODE", False)
10
19
 
11
20
 
@@ -15,99 +24,102 @@ class SquelchStackTraces(Filter):
15
24
  return super().filter(record)
16
25
 
17
26
 
18
- LOGGING = {
19
- "version": 1,
20
- "disable_existing_loggers": False,
21
- "handlers": {
22
- "file": {
23
- "level": DEFAULT_LOG_LEVEL, # set in deployment routine
24
- "class": "logging.handlers.TimedRotatingFileHandler",
25
- "formatter": "verbose" if DEBUG else "simple", # set in deployment routine
26
- "filename": LOG_DIR
27
- / f'{get_setting("SLM_SITE_NAME", "").lower()}{"_manage" if SLM_MANAGEMENT_MODE else ""}.log',
28
- "when": "midnight",
29
- "interval": 1,
30
- "backupCount": 14,
31
- },
32
- "mail_admins": {
33
- "level": "ERROR",
34
- "class": "django.utils.log.AdminEmailHandler",
35
- "include_html": True,
36
- },
37
- },
38
- "formatters": {
39
- "verbose": {
40
- "format": "%(levelname)s %(process)d %(asctime)s %(name)s %(process)d %(thread)d %(message)s"
27
+ if not is_defined("LOGGING"):
28
+ LOGGING = {
29
+ "version": 1,
30
+ "disable_existing_loggers": False,
31
+ "handlers": {
32
+ "file": {
33
+ "level": SLM_LOG_LEVEL, # set in deployment routine
34
+ "class": "logging.handlers.TimedRotatingFileHandler",
35
+ "formatter": "verbose"
36
+ if DEBUG
37
+ else "simple", # set in deployment routine
38
+ "filename": SLM_LOG_DIR
39
+ / f"{get_setting('SLM_SITE_NAME', '').lower()}{'_manage' if SLM_MANAGEMENT_MODE else ''}.log",
40
+ "when": "midnight",
41
+ "interval": 1,
42
+ "backupCount": 14,
43
+ },
44
+ "mail_admins": {
45
+ "level": "ERROR",
46
+ "class": "django.utils.log.AdminEmailHandler",
47
+ "include_html": True,
48
+ },
41
49
  },
42
- "simple": {
43
- "format": "%(levelname)s %(process)d %(asctime)s %(name)s %(message)s"
50
+ "formatters": {
51
+ "verbose": {
52
+ "format": "%(levelname)s %(process)d %(asctime)s %(name)s %(process)d %(thread)d %(message)s"
53
+ },
54
+ "simple": {
55
+ "format": "%(levelname)s %(process)d %(asctime)s %(name)s %(message)s"
56
+ },
57
+ "management": {"format": "%(message)s"},
44
58
  },
45
- "management": {"format": "%(message)s"},
46
- },
47
- "filters": {
48
- "squelch_traces": {
49
- "()": SquelchStackTraces,
59
+ "filters": {
60
+ "squelch_traces": {
61
+ "()": SquelchStackTraces,
62
+ },
50
63
  },
51
- },
52
- "loggers": {
53
- "django.request": {
54
- "handlers": ["mail_admins"],
55
- "level": "ERROR",
56
- "propagate": True,
57
- },
58
- "django": {
59
- "handlers": ["file"],
60
- "level": DEFAULT_LOG_LEVEL,
61
- "propagate": False,
64
+ "loggers": {
65
+ "django.request": {
66
+ "handlers": ["mail_admins"],
67
+ "level": "ERROR",
68
+ "propagate": True,
69
+ },
70
+ "django": {
71
+ "handlers": ["file"],
72
+ "level": SLM_LOG_LEVEL,
73
+ "propagate": False,
74
+ },
75
+ "django.db.backends": {
76
+ "handlers": ["file"],
77
+ "level": "INFO", # super noisy
78
+ "propagate": False,
79
+ },
80
+ "django.template": {
81
+ "handlers": ["file"],
82
+ "filters": ["squelch_traces"],
83
+ "level": "INFO",
84
+ "propagate": False,
85
+ },
86
+ "django.utils.autoreload": {
87
+ "handlers": ["file"],
88
+ "level": "WARNING", # this logger got really noisy in django 2.2
89
+ "propagate": False,
90
+ },
91
+ "django_auth_ldap": {"level": SLM_LOG_LEVEL, "propagate": True},
62
92
  },
63
- "django.db.backends": {
64
- "handlers": ["file"],
65
- "level": "INFO", # super noisy
66
- "propagate": False,
93
+ "root": {
94
+ "handlers": ["file", "mail_admins"],
95
+ "level": SLM_LOG_LEVEL, # set in deployment routine
67
96
  },
68
- "django.template": {
69
- "handlers": ["file"],
70
- "filters": ["squelch_traces"],
71
- "level": "INFO",
72
- "propagate": False,
73
- },
74
- "django.utils.autoreload": {
97
+ }
98
+
99
+ if DEBUG and not SLM_MANAGEMENT_MODE:
100
+ LOGGING["loggers"]["core.middleware.RequestLogger"] = {
75
101
  "handlers": ["file"],
76
- "level": "WARNING", # this logger got really noisy in django 2.2
102
+ "level": "DEBUG",
77
103
  "propagate": False,
78
- },
79
- "django_auth_ldap": {"level": DEFAULT_LOG_LEVEL, "propagate": True},
80
- },
81
- "root": {
82
- "handlers": ["file", "mail_admins"],
83
- "level": DEFAULT_LOG_LEVEL, # set in deployment routine
84
- },
85
- }
104
+ }
86
105
 
87
- if DEBUG and not SLM_MANAGEMENT_MODE:
88
- LOGGING["loggers"]["core.middleware.RequestLogger"] = {
89
- "handlers": ["file"],
90
- "level": "DEBUG",
91
- "propagate": False,
92
- }
93
-
94
- if SLM_MANAGEMENT_MODE:
95
- LOGGING.setdefault("handlers", {})["console"] = {
96
- "level": "INFO",
97
- "class": "logging.StreamHandler",
98
- "formatter": "management",
99
- }
106
+ if SLM_MANAGEMENT_MODE:
107
+ LOGGING.setdefault("handlers", {})["console"] = {
108
+ "level": "INFO",
109
+ "class": "logging.StreamHandler",
110
+ "formatter": "management",
111
+ }
100
112
 
101
- if "root" in LOGGING:
102
- if "console" not in LOGGING["root"]["handlers"]:
103
- LOGGING["root"]["handlers"].append("console")
113
+ if "root" in LOGGING:
114
+ if "console" not in LOGGING["root"]["handlers"]:
115
+ LOGGING["root"]["handlers"].append("console")
104
116
 
105
- for name, config in LOGGING.get("loggers", {}).items():
106
- if "handlers" in config and "console" not in config["handlers"]:
107
- config["handlers"].append("console")
117
+ for name, config in LOGGING.get("loggers", {}).items():
118
+ if "handlers" in config and "console" not in config["handlers"]:
119
+ config["handlers"].append("console")
108
120
 
109
- # create logging dirs if necessary
110
- for name, handler_spec in LOGGING["handlers"].items():
111
- filename = handler_spec.get("filename", None)
112
- if filename:
113
- os.makedirs(os.path.dirname(filename), exist_ok=True)
121
+ # create logging dirs if necessary
122
+ for name, handler_spec in LOGGING["handlers"].items():
123
+ filename = handler_spec.get("filename", None)
124
+ if filename:
125
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
@@ -1,10 +1,20 @@
1
1
  # this is the default location Postgres.app with postgres will install the postgis libraries to
2
2
  # if your system is different, or you use a different postgres package you will have to
3
3
  # override these
4
+ from pathlib import Path
4
5
 
5
- GDAL_LIBRARY_PATH = (
6
- "/Applications/Postgres.app/Contents/Versions/latest/lib/libgdal.dylib"
7
- )
8
- GEOS_LIBRARY_PATH = (
9
- "/Applications/Postgres.app/Contents/Versions/latest/lib/libgeos_c.dylib"
10
- )
6
+ from slm.settings import get_setting
7
+
8
+ if not get_setting("GDAL_LIBRARY_PATH"):
9
+ gdal_postgres_app_path = Path(
10
+ "/Applications/Postgres.app/Contents/Versions/latest/lib/libgdal.dylib"
11
+ )
12
+ if gdal_postgres_app_path.is_file():
13
+ GDAL_LIBRARY_PATH = str(gdal_postgres_app_path)
14
+
15
+ if not get_setting("GEOS_LIBRARY_PATH"):
16
+ geos_postgres_app_path = Path(
17
+ "/Applications/Postgres.app/Contents/Versions/latest/lib/libgeos_c.dylib"
18
+ )
19
+ if geos_postgres_app_path.is_file():
20
+ GEOS_LIBRARY_PATH = str(geos_postgres_app_path)