igs-slm 0.1.4b0__py3-none-any.whl → 0.1.5b1__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 (151) hide show
  1. igs_slm-0.1.5b1.dist-info/METADATA +115 -0
  2. {igs_slm-0.1.4b0.dist-info → igs_slm-0.1.5b1.dist-info}/RECORD +193 -173
  3. {igs_slm-0.1.4b0.dist-info → igs_slm-0.1.5b1.dist-info}/WHEEL +1 -1
  4. igs_slm-0.1.5b1.dist-info/entry_points.txt +3 -0
  5. {igs_slm-0.1.4b0.dist-info → igs_slm-0.1.5b1.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 +9 -7
  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 }}/urls.py +15 -0
  19. slm/bin/templates/{{ project_dir }}/src/sites/{{ site }}/validation.py +29 -0
  20. slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/urls.py +1 -0
  21. slm/context.py +5 -0
  22. slm/defines/AlertLevel.py +10 -2
  23. slm/defines/AntennaCalibration.py +6 -4
  24. slm/defines/AntennaFeatures.py +12 -9
  25. slm/defines/AntennaReferencePoint.py +12 -10
  26. slm/defines/Aspiration.py +4 -2
  27. slm/defines/CardinalDirection.py +6 -4
  28. slm/defines/CollocationStatus.py +3 -1
  29. slm/defines/EquipmentState.py +6 -12
  30. slm/defines/FlagSeverity.py +4 -2
  31. slm/defines/FractureSpacing.py +7 -5
  32. slm/defines/FrequencyStandardType.py +6 -4
  33. slm/defines/GeodesyMLVersion.py +2 -0
  34. slm/defines/Instrumentation.py +9 -7
  35. slm/defines/LogEntryType.py +17 -15
  36. slm/defines/SLMFileType.py +4 -2
  37. slm/defines/SiteFileUploadStatus.py +7 -24
  38. slm/defines/SiteLogFormat.py +8 -32
  39. slm/defines/SiteLogStatus.py +9 -28
  40. slm/defines/TectonicPlates.py +18 -16
  41. slm/manage.py +24 -0
  42. slm/management/commands/check_upgrade.py +142 -0
  43. slm/management/commands/generate_sinex.py +110 -92
  44. slm/management/commands/head_from_index.py +11 -8
  45. slm/management/commands/import_archive.py +27 -18
  46. slm/management/commands/import_equipment.py +1 -1
  47. slm/management/commands/sitelog.py +1 -3
  48. slm/management/commands/synchronize.py +1 -1
  49. slm/management/commands/validate_db.py +6 -4
  50. slm/map/defines.py +18 -14
  51. slm/map/templates/slm/map.html +5 -7
  52. slm/migrations/0001_remove_archiveindex_no_overlapping_ranges_per_site_and_more.py +26 -0
  53. slm/migrations/0002_alter_archivedsitelog_file_and_more.py +44 -0
  54. slm/migrations/0003_alter_archivedsitelog_name_and_more.py +35 -0
  55. slm/migrations/0004_alter_site_name.py +24 -0
  56. slm/migrations/0005_slmversion.py +30 -0
  57. slm/migrations/0017_alter_logentry_unique_together_and_more.py +3 -1
  58. slm/migrations/0018_afix_deleted.py +3 -1
  59. slm/migrations/0018_alter_siteantenna_options_and_more.py +87 -56
  60. slm/migrations/0019_remove_siteantenna_marker_enu_siteantenna_marker_une_and_more.py +1 -1
  61. slm/migrations/0023_archivedsitelog_gml_version_and_more.py +1 -1
  62. slm/migrations/0031_alter_antenna_features.py +44 -0
  63. slm/migrations/0032_archiveindex_valid_range_and_more.py +84 -0
  64. slm/migrations/add_index_order_index.py +54 -0
  65. slm/migrations/normalize_index.py +147 -0
  66. slm/migrations/simplify_index.py +48 -0
  67. slm/migrations/verify_index.py +67 -0
  68. slm/models/__init__.py +2 -0
  69. slm/models/alerts.py +7 -10
  70. slm/models/data.py +1 -2
  71. slm/models/equipment.py +1 -1
  72. slm/models/fields.py +41 -0
  73. slm/models/index.py +183 -53
  74. slm/models/sitelog.py +35 -38
  75. slm/models/system.py +72 -31
  76. slm/models/user.py +1 -1
  77. slm/parsing/__init__.py +33 -16
  78. slm/parsing/legacy/binding.py +65 -34
  79. slm/parsing/legacy/parser.py +2 -2
  80. slm/parsing/xsd/binding.py +1 -1
  81. slm/parsing/xsd/parser.py +1 -2
  82. slm/receivers/__init__.py +2 -2
  83. slm/receivers/index.py +2 -1
  84. slm/receivers/migration.py +21 -0
  85. slm/settings/__init__.py +192 -4
  86. slm/settings/assets.py +26 -0
  87. slm/settings/auth.py +18 -14
  88. slm/settings/ckeditor.py +12 -6
  89. slm/settings/debug.py +2 -2
  90. slm/settings/emails.py +50 -0
  91. slm/settings/internationalization.py +8 -6
  92. slm/settings/logging.py +100 -88
  93. slm/settings/platform/darwin.py +16 -6
  94. slm/settings/rest.py +20 -15
  95. slm/settings/root.py +192 -98
  96. slm/settings/routines.py +5 -1
  97. slm/settings/secrets.py +20 -31
  98. slm/settings/security.py +7 -5
  99. slm/settings/slm.py +35 -23
  100. slm/settings/static_templates.py +12 -9
  101. slm/settings/templates.py +31 -25
  102. slm/settings/uploads.py +33 -5
  103. slm/settings/urls.py +7 -12
  104. slm/settings/validation.py +165 -165
  105. slm/signals.py +3 -2
  106. slm/static/slm/css/style.css +37 -36
  107. slm/static/slm/js/autocomplete.js +6 -4
  108. slm/static/slm/js/enums.js +6 -5
  109. slm/static/slm/js/file_modal.js +62 -0
  110. slm/static/slm/js/form.js +3 -3
  111. slm/static/slm/js/formWidget.js +3 -3
  112. slm/static/slm/js/persistable.js +5 -1
  113. slm/templates/admin/base.html +1 -0
  114. slm/templates/rest_framework/base.html +23 -11
  115. slm/templates/slm/base.html +27 -22
  116. slm/templates/slm/forms/widgets/auto_complete.html +12 -11
  117. slm/templates/slm/forms/widgets/auto_complete_multiple.html +8 -7
  118. slm/templates/slm/station/download.html +6 -6
  119. slm/templates/slm/station/edit.html +9 -17
  120. slm/templates/slm/station/review.html +5 -3
  121. slm/templates/slm/station/upload.html +4 -1
  122. slm/templates/slm/station/uploads/legacy.html +1 -1
  123. slm/templates/slm/widgets/alert_scroll.html +4 -8
  124. slm/templates/slm/widgets/filelist.html +0 -5
  125. slm/templates/slm/widgets/log_scroll.html +2 -13
  126. slm/templates/slm/widgets/stationlist.html +2 -8
  127. slm/templatetags/slm.py +70 -9
  128. slm/utils.py +13 -4
  129. slm/validators.py +14 -14
  130. slm/views.py +6 -6
  131. slm/wsgi.py +16 -0
  132. igs_slm-0.1.4b0.dist-info/METADATA +0 -154
  133. igs_slm-0.1.4b0.dist-info/entry_points.txt +0 -3
  134. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/urls.py +0 -7
  135. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/validation.py +0 -11
  136. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/__init__.py +0 -0
  137. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/__init__.py +0 -0
  138. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/develop/local.py +0 -0
  139. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/develop/wsgi.py +0 -0
  140. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/manage.py +0 -0
  141. /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/production/wsgi.py +0 -0
  142. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/__init__.py +0 -0
  143. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/admin.py +0 -0
  144. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/apps.py +0 -0
  145. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/management/__init__.py +0 -0
  146. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/management/commands/__init__.py +0 -0
  147. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/management/commands/import_archive.py +0 -0
  148. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/migrations/__init__.py +0 -0
  149. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/models.py +0 -0
  150. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/templates/slm/base.html +0 -0
  151. /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/views.py +0 -0
@@ -13,30 +13,13 @@ class SiteFileUploadStatus(IntegerChoices, p("help")):
13
13
 
14
14
  _symmetric_builtins_ = [s("name", case_fold=True)]
15
15
 
16
- UNPUBLISHED = (
17
- 1,
18
- _("Unpublished File"),
19
- _("The file is pending moderation before it will be made public."),
20
- )
21
-
22
- PUBLISHED = (
23
- 2,
24
- _("Published File"),
25
- _(
26
- "The file is published and is publicly available as an attachment "
27
- "to the site."
28
- ),
29
- )
30
-
31
- INVALID = (3, _("Invalid Site Log"), _("The file did not pass validation."))
32
-
33
- WARNINGS = (
34
- 4,
35
- _("Warnings Site Log"),
36
- _("The file is valid but has some warnings."),
37
- )
38
-
39
- VALID = (5, _("Valid Site Log"), _("The file is valid."))
16
+ # fmt: off
17
+ UNPUBLISHED = 1, _("Unpublished File"), _("The file is pending moderation before it will be made public.")
18
+ PUBLISHED = 2, _("Published File"), _("The file is published and is publicly available as an attachment to the site.")
19
+ INVALID = 3, _("Invalid Site Log"), _("The file did not pass validation.")
20
+ WARNINGS = 4, _("Warnings Site Log"), _("The file is valid but has some warnings.")
21
+ VALID = 5, _("Valid Site Log"), _("The file is valid.")
22
+ # fmt: on
40
23
 
41
24
  @classmethod
42
25
  def status_by_filetype(cls, filetype):
@@ -9,41 +9,17 @@ class SiteLogFormat(
9
9
  p("icon"),
10
10
  s("ext", case_fold=True),
11
11
  s("alts", case_fold=True),
12
+ s("alt_exts", case_fold=True),
12
13
  ):
13
14
  _symmetric_builtins_ = [s("name", case_fold=True)]
14
15
 
15
- LEGACY = (
16
- 1,
17
- _("Legacy (ASCII)"),
18
- "text/plain",
19
- "bi bi-file-text",
20
- "log",
21
- ["text", "txt", "legacy"],
22
- )
23
- GEODESY_ML = (
24
- 2,
25
- _("GeodesyML"),
26
- "application/xml",
27
- "bi bi-filetype-xml",
28
- "xml",
29
- ["xml"],
30
- )
31
- JSON = (
32
- 3,
33
- _("JSON"),
34
- "application/json",
35
- "bi bi-filetype-json",
36
- "json",
37
- ["json", "js"],
38
- )
39
- ASCII_9CHAR = (
40
- 4,
41
- _("ASCII (9-Char)"),
42
- "text/plain",
43
- "bi bi-file-text",
44
- "log",
45
- ["text", "txt", "9char"],
46
- )
16
+ # fmt: off
17
+ # name value label mimetype icon ext alts alt_exts
18
+ LEGACY = 1, _("Legacy (ASCII)"), "text/plain", "bi bi-file-text", "log", ["text", "txt", "legacy"], ["txt", "sitelog"]
19
+ GEODESY_ML = 2, _("GeodesyML"), "application/xml", "bi bi-filetype-xml", "xml", ["xml"], ["gml"]
20
+ JSON = 3, _("JSON"), "application/json", "bi bi-filetype-json", "json", ["json", "js"], ["js"]
21
+ ASCII_9CHAR = 4, _("ASCII (9-Char)"), "text/plain", "bi bi-file-text", "log", ["text", "txt", "9char"], ["txt", "sitelog"]
22
+ # fmt: on
47
23
 
48
24
  def __str__(self):
49
25
  return str(self.label)
@@ -6,37 +6,18 @@ from enum_properties import p, s
6
6
  class SiteLogStatus(IntegerChoices, p("help")):
7
7
  _symmetric_builtins_ = [s("name", case_fold=True)]
8
8
 
9
- FORMER = (
10
- 1,
11
- _("Former"),
12
- _("Site is no longer maintained and logs are not published."),
13
- )
14
-
15
- PROPOSED = (
16
- 2,
17
- _("Proposed"),
18
- _("This is a new Site that has never been published."),
19
- )
20
-
21
- UPDATED = (3, _("Updated"), _("Site log or section has unpublished updates."))
22
-
23
- PUBLISHED = (
24
- 4,
25
- _("Published"),
26
- _("Site log or section is published with no unpublished changes."),
27
- )
28
-
29
- EMPTY = (5, _("Empty"), _("Site log section is empty or deleted."))
30
-
31
- SUSPENDED = (
32
- 6,
33
- _("Suspended"),
34
- _("Site has been temporarily suspended and does not appear in public" " data."),
35
- )
9
+ # fmt: off
10
+ FORMER = 1, _("Former"), _("Site is no longer maintained and logs are not published.")
11
+ PROPOSED = 2, _("Proposed"), _("This is a new Site that has never been published.")
12
+ UPDATED = 3, _("Updated"), _("Site log or section has unpublished updates.")
13
+ PUBLISHED = 4, _("Published"), _("Site log or section is published with no unpublished changes.")
14
+ EMPTY = 5, _("Empty"), _("Site log section is empty or deleted.")
15
+ SUSPENDED = 6, _("Suspended"), _("Site has been temporarily suspended and does not appear in public data.")
16
+ # fmt: on
36
17
 
37
18
  @property
38
19
  def css(self):
39
- return f'slm-status-{self.label.lower().replace(" ", "-")}'
20
+ return f"slm-status-{self.label.lower().replace(' ', '-')}"
40
21
 
41
22
  @property
42
23
  def color(self):
@@ -5,24 +5,26 @@ from enum_properties import s
5
5
  class TectonicPlates(TextChoices):
6
6
  _symmetric_builtins_ = [s("name", case_fold=True), s("label", case_fold=True)]
7
7
 
8
- AFRICAN = "AF", "African"
9
- ANTARCTIC = "AN", "Antarctic"
10
- ARABIAN = "AR", "Arabian"
11
- AUSTRALIAN = "AU", "Australian"
12
- CARIBBEAN = "CA", "Caribbean"
13
- COCOS = "CO", "Cocos"
14
- EURASIAN = "EU", "Eurasian"
15
- INDIAN = "IN", "Indian"
16
- JUAN_DE_FUCA = "JU", "Juan de Fuca"
17
- NAZCA = "NZ", "Nazca"
8
+ # fmt: off
9
+ AFRICAN = "AF", "African"
10
+ ANTARCTIC = "AN", "Antarctic"
11
+ ARABIAN = "AR", "Arabian"
12
+ AUSTRALIAN = "AU", "Australian"
13
+ CARIBBEAN = "CA", "Caribbean"
14
+ COCOS = "CO", "Cocos"
15
+ EURASIAN = "EU", "Eurasian"
16
+ INDIAN = "IN", "Indian"
17
+ JUAN_DE_FUCA = "JU", "Juan de Fuca"
18
+ NAZCA = "NZ", "Nazca"
18
19
  NORTH_AMERICAN = "NA", "North American"
19
- PACIFIC = "PA", "Pacific"
20
- PHILIPPINE = "PH", "Philippine"
21
- SCOTIA = "SC", "Scotia"
20
+ PACIFIC = "PA", "Pacific"
21
+ PHILIPPINE = "PH", "Philippine"
22
+ SCOTIA = "SC", "Scotia"
22
23
  SOUTH_AMERICAN = "SA", "South American"
23
- NUBIA = "NU", "Nubia"
24
- SOMALIA = "SO", "Somalia"
25
- MARIANA = "MA", "Mariana"
24
+ NUBIA = "NU", "Nubia"
25
+ SOMALIA = "SO", "Somalia"
26
+ MARIANA = "MA", "Mariana"
27
+ # fmt: on
26
28
 
27
29
  def __str__(self):
28
30
  return str(self.label)
slm/manage.py ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env python
2
+ """Django's command-line utility for administrative tasks."""
3
+
4
+ import os
5
+ import sys
6
+
7
+ from django.core.management import execute_from_command_line
8
+
9
+
10
+ def main():
11
+ """Run administrative tasks."""
12
+
13
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "slm.settings.root")
14
+ # We use a slightly different logger config if we're running a management command vs serving
15
+ if len(sys.argv) > 1:
16
+ subcommand = sys.argv[1]
17
+ if subcommand != "runserver":
18
+ os.environ["SLM_MANAGEMENT_FLAG"] = "ON"
19
+ ##################################################
20
+ execute_from_command_line(sys.argv)
21
+
22
+
23
+ if __name__ == "__main__":
24
+ main()
@@ -0,0 +1,142 @@
1
+ """
2
+ Occasionally it is helpful to delete all the migration files and start anew with one
3
+ simple initial migration. Changes accumulate over time, or old migration files may
4
+ cause errors on new software stacks. This command facilitates a transition back to
5
+ one initial migration file on a version upgrade. One side effect of this is that you
6
+ may need to migrate through multiple versions instead of being able to make one big
7
+ jump. This command will check for these cases and fail if the migration from the
8
+ current software version is unsafe on the current database version.
9
+ """
10
+
11
+ import bisect
12
+ import typing as t
13
+
14
+ import typer
15
+ from django.core.management import CommandError
16
+ from django.db import connection
17
+ from django.db.migrations.loader import MigrationLoader
18
+ from django.utils.translation import gettext as _
19
+ from django_typer.management import TyperCommand, command
20
+ from packaging.version import InvalidVersion, Version, parse
21
+ from typing_extensions import Annotated
22
+
23
+ from slm import __version__ as slm_version
24
+ from slm.models import SLMVersion
25
+
26
+
27
+ def parse_version(version: str) -> Version:
28
+ try:
29
+ parse(version)
30
+ except InvalidVersion as verr:
31
+ raise typer.BadParameter(
32
+ f"{version} is not a valid Python package version string."
33
+ ) from verr
34
+
35
+
36
+ def get_ordered_migration_paths(app_label):
37
+ loader = MigrationLoader(connection)
38
+ graph = loader.graph
39
+
40
+ app_migrations = [key for key in graph.nodes.keys() if key[0] == app_label]
41
+
42
+ if not app_migrations:
43
+ raise ValueError(f"No migrations found for app '{app_label}'")
44
+
45
+ latest_keys = graph.leaf_nodes(app_label)
46
+
47
+ # Get the full dependency plan (in order)
48
+ plan = []
49
+ for latest in latest_keys:
50
+ plan.extend(graph.forwards_plan(latest))
51
+
52
+ # De-duplicate and filter only this app's migrations
53
+ seen = set()
54
+ ordered_keys = [
55
+ m for m in plan if m[0] == app_label and not (m in seen or seen.add(m))
56
+ ]
57
+
58
+ return [loader.get_migration(app, name) for app, name in ordered_keys]
59
+
60
+
61
+ class Command(TyperCommand):
62
+ help = _(
63
+ "Make sure the attempted upgrade is safe. And if it is, make any migration table "
64
+ "edits that are necessary."
65
+ )
66
+
67
+ # when upgrading the SLM it is required that an upgrade run through
68
+ # these specific versions. These versions are points at which the migration
69
+ # files were remade - meaning the database state may not be migrated correctly
70
+ # if it was not first updated to be state consistent with these specific versions.
71
+ VERSION_WAYPOINTS = list(sorted([parse("0.1.5b")]))
72
+
73
+ requires_migrations_checks = False
74
+ requires_system_checks = []
75
+
76
+ def closest_waypoint_gte(self, version: Version) -> t.Optional[Version]:
77
+ idx = bisect.bisect_left(self.VERSION_WAYPOINTS, version)
78
+ if idx < len(self.VERSION_WAYPOINTS):
79
+ return self.VERSION_WAYPOINTS[idx]
80
+ return None
81
+
82
+ def closest_waypoint_lte(self, version: Version) -> t.Optional[Version]:
83
+ idx = bisect.bisect_right(self.VERSION_WAYPOINTS, version)
84
+ if idx > 0:
85
+ return self.VERSION_WAYPOINTS[idx - 1]
86
+ return None
87
+
88
+ @command(
89
+ help="Check that it is safe to run migrations from the installed version of igs-slm."
90
+ )
91
+ def is_safe(self):
92
+ db_version: Version = SLMVersion.load().version or parse("0.1.4b")
93
+ if db_version > slm_version:
94
+ # downgrades are possible with reversible migrations - we only
95
+ # balk if the downgrade would travel through a migration waypoint
96
+ nearest_waypoint = self.closest_waypoint_lte(db_version)
97
+ if nearest_waypoint and nearest_waypoint > slm_version:
98
+ raise CommandError(
99
+ f"Unable to downgrade from {db_version} to {slm_version}. "
100
+ f"Traverses version waypoint: {nearest_waypoint}. "
101
+ f"It is recommended that you restore from a database backup."
102
+ )
103
+ elif db_version < slm_version:
104
+ # Upgrades must pass through all waypoints between the database code version and
105
+ # the installed version of igs-slm
106
+ nearest_waypoint = self.closest_waypoint_gte(db_version)
107
+ if nearest_waypoint and nearest_waypoint < slm_version:
108
+ raise CommandError(
109
+ f"Unable to upgrade from {db_version} to {slm_version}. "
110
+ f"Traverses version waypoint: {nearest_waypoint}. "
111
+ f"You must first install and upgrade SLM at the waypoint: "
112
+ f"pip install igs-slm=={nearest_waypoint}."
113
+ )
114
+
115
+ @command(
116
+ help="Force the database igs-slm version to the installed igs-slm version or the given value."
117
+ )
118
+ def set_db_version(
119
+ self,
120
+ version: Annotated[
121
+ Version,
122
+ typer.Option(
123
+ help="The version string to set in the database if different than installed.",
124
+ parser=parse_version,
125
+ ),
126
+ ],
127
+ ):
128
+ from slm import __version__ as slm_version
129
+
130
+ version = version or parse_version(slm_version)
131
+ if version != SLMVersion.load().version:
132
+ confirm = typer.confirm(
133
+ _(
134
+ "You are about to force the database to record the version of the igs-slm "
135
+ f"software it is structurally synchronized to "
136
+ f"({SLMVersion.load().version} -> {version}). Are you sure you want to do this?"
137
+ )
138
+ )
139
+ if not confirm:
140
+ self.secho(_("Aborted."), fg="red")
141
+ raise typer.Exit(code=1)
142
+ SLMVersion.update(version=version)