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.
- igs_slm-0.1.5b1.dist-info/METADATA +115 -0
- {igs_slm-0.1.4b0.dist-info → igs_slm-0.1.5b1.dist-info}/RECORD +193 -173
- {igs_slm-0.1.4b0.dist-info → igs_slm-0.1.5b1.dist-info}/WHEEL +1 -1
- igs_slm-0.1.5b1.dist-info/entry_points.txt +3 -0
- {igs_slm-0.1.4b0.dist-info → igs_slm-0.1.5b1.dist-info/licenses}/LICENSE +1 -1
- slm/__init__.py +17 -14
- slm/admin.py +32 -5
- slm/api/edit/views.py +22 -9
- slm/api/public/views.py +9 -7
- slm/api/views.py +45 -6
- slm/apps.py +28 -6
- slm/authentication.py +3 -2
- slm/bin/startproject.py +102 -31
- slm/bin/templates/{{ project_dir }}/pyproject.toml +30 -21
- slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/base.py +12 -1
- slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/develop/__init__.py +5 -27
- slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/production/__init__.py +6 -27
- slm/bin/templates/{{ project_dir }}/src/sites/{{ site }}/urls.py +15 -0
- slm/bin/templates/{{ project_dir }}/src/sites/{{ site }}/validation.py +29 -0
- slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/urls.py +1 -0
- slm/context.py +5 -0
- slm/defines/AlertLevel.py +10 -2
- slm/defines/AntennaCalibration.py +6 -4
- slm/defines/AntennaFeatures.py +12 -9
- slm/defines/AntennaReferencePoint.py +12 -10
- slm/defines/Aspiration.py +4 -2
- slm/defines/CardinalDirection.py +6 -4
- slm/defines/CollocationStatus.py +3 -1
- slm/defines/EquipmentState.py +6 -12
- slm/defines/FlagSeverity.py +4 -2
- slm/defines/FractureSpacing.py +7 -5
- slm/defines/FrequencyStandardType.py +6 -4
- slm/defines/GeodesyMLVersion.py +2 -0
- slm/defines/Instrumentation.py +9 -7
- slm/defines/LogEntryType.py +17 -15
- slm/defines/SLMFileType.py +4 -2
- slm/defines/SiteFileUploadStatus.py +7 -24
- slm/defines/SiteLogFormat.py +8 -32
- slm/defines/SiteLogStatus.py +9 -28
- slm/defines/TectonicPlates.py +18 -16
- slm/manage.py +24 -0
- slm/management/commands/check_upgrade.py +142 -0
- slm/management/commands/generate_sinex.py +110 -92
- slm/management/commands/head_from_index.py +11 -8
- slm/management/commands/import_archive.py +27 -18
- slm/management/commands/import_equipment.py +1 -1
- slm/management/commands/sitelog.py +1 -3
- slm/management/commands/synchronize.py +1 -1
- slm/management/commands/validate_db.py +6 -4
- slm/map/defines.py +18 -14
- slm/map/templates/slm/map.html +5 -7
- slm/migrations/0001_remove_archiveindex_no_overlapping_ranges_per_site_and_more.py +26 -0
- slm/migrations/0002_alter_archivedsitelog_file_and_more.py +44 -0
- slm/migrations/0003_alter_archivedsitelog_name_and_more.py +35 -0
- slm/migrations/0004_alter_site_name.py +24 -0
- slm/migrations/0005_slmversion.py +30 -0
- slm/migrations/0017_alter_logentry_unique_together_and_more.py +3 -1
- slm/migrations/0018_afix_deleted.py +3 -1
- slm/migrations/0018_alter_siteantenna_options_and_more.py +87 -56
- slm/migrations/0019_remove_siteantenna_marker_enu_siteantenna_marker_une_and_more.py +1 -1
- slm/migrations/0023_archivedsitelog_gml_version_and_more.py +1 -1
- slm/migrations/0031_alter_antenna_features.py +44 -0
- slm/migrations/0032_archiveindex_valid_range_and_more.py +84 -0
- slm/migrations/add_index_order_index.py +54 -0
- slm/migrations/normalize_index.py +147 -0
- slm/migrations/simplify_index.py +48 -0
- slm/migrations/verify_index.py +67 -0
- slm/models/__init__.py +2 -0
- slm/models/alerts.py +7 -10
- slm/models/data.py +1 -2
- slm/models/equipment.py +1 -1
- slm/models/fields.py +41 -0
- slm/models/index.py +183 -53
- slm/models/sitelog.py +35 -38
- slm/models/system.py +72 -31
- slm/models/user.py +1 -1
- slm/parsing/__init__.py +33 -16
- slm/parsing/legacy/binding.py +65 -34
- slm/parsing/legacy/parser.py +2 -2
- slm/parsing/xsd/binding.py +1 -1
- slm/parsing/xsd/parser.py +1 -2
- slm/receivers/__init__.py +2 -2
- slm/receivers/index.py +2 -1
- slm/receivers/migration.py +21 -0
- slm/settings/__init__.py +192 -4
- slm/settings/assets.py +26 -0
- slm/settings/auth.py +18 -14
- slm/settings/ckeditor.py +12 -6
- slm/settings/debug.py +2 -2
- slm/settings/emails.py +50 -0
- slm/settings/internationalization.py +8 -6
- slm/settings/logging.py +100 -88
- slm/settings/platform/darwin.py +16 -6
- slm/settings/rest.py +20 -15
- slm/settings/root.py +192 -98
- slm/settings/routines.py +5 -1
- slm/settings/secrets.py +20 -31
- slm/settings/security.py +7 -5
- slm/settings/slm.py +35 -23
- slm/settings/static_templates.py +12 -9
- slm/settings/templates.py +31 -25
- slm/settings/uploads.py +33 -5
- slm/settings/urls.py +7 -12
- slm/settings/validation.py +165 -165
- slm/signals.py +3 -2
- slm/static/slm/css/style.css +37 -36
- slm/static/slm/js/autocomplete.js +6 -4
- slm/static/slm/js/enums.js +6 -5
- slm/static/slm/js/file_modal.js +62 -0
- slm/static/slm/js/form.js +3 -3
- slm/static/slm/js/formWidget.js +3 -3
- slm/static/slm/js/persistable.js +5 -1
- slm/templates/admin/base.html +1 -0
- slm/templates/rest_framework/base.html +23 -11
- slm/templates/slm/base.html +27 -22
- slm/templates/slm/forms/widgets/auto_complete.html +12 -11
- slm/templates/slm/forms/widgets/auto_complete_multiple.html +8 -7
- slm/templates/slm/station/download.html +6 -6
- slm/templates/slm/station/edit.html +9 -17
- slm/templates/slm/station/review.html +5 -3
- slm/templates/slm/station/upload.html +4 -1
- slm/templates/slm/station/uploads/legacy.html +1 -1
- slm/templates/slm/widgets/alert_scroll.html +4 -8
- slm/templates/slm/widgets/filelist.html +0 -5
- slm/templates/slm/widgets/log_scroll.html +2 -13
- slm/templates/slm/widgets/stationlist.html +2 -8
- slm/templatetags/slm.py +70 -9
- slm/utils.py +13 -4
- slm/validators.py +14 -14
- slm/views.py +6 -6
- slm/wsgi.py +16 -0
- igs_slm-0.1.4b0.dist-info/METADATA +0 -154
- igs_slm-0.1.4b0.dist-info/entry_points.txt +0 -3
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/urls.py +0 -7
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/validation.py +0 -11
- /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/__init__.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/__init__.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/develop/local.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/develop/wsgi.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/manage.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{sites → src/sites}/{{ site }}/production/wsgi.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/__init__.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/admin.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/apps.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/management/__init__.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/management/commands/__init__.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/management/commands/import_archive.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/migrations/__init__.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/models.py +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/templates/slm/base.html +0 -0
- /slm/bin/templates/{{ project_dir }}/{{{ extension_app }} → src/{{ extension_app }}}/views.py +0 -0
slm/models/system.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import typing as t
|
|
2
3
|
from datetime import datetime, timezone
|
|
3
4
|
from io import BytesIO
|
|
4
5
|
from logging import getLogger
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
|
|
6
8
|
from dateutil import parser
|
|
7
9
|
from django.conf import settings
|
|
@@ -14,6 +16,8 @@ from django.urls import reverse
|
|
|
14
16
|
from django.utils.timezone import is_naive, make_aware, now
|
|
15
17
|
from django.utils.translation import gettext as _
|
|
16
18
|
from django_enum import EnumField
|
|
19
|
+
from packaging.version import Version
|
|
20
|
+
from packaging.version import parse as parse_version
|
|
17
21
|
from PIL import Image
|
|
18
22
|
from polymorphic.managers import PolymorphicManager, PolymorphicQuerySet
|
|
19
23
|
from polymorphic.models import PolymorphicModel
|
|
@@ -27,9 +31,46 @@ from slm.defines import (
|
|
|
27
31
|
SLMFileType,
|
|
28
32
|
)
|
|
29
33
|
from slm.models.sitelog import DefaultToStrEncoder
|
|
34
|
+
from slm.singleton import SingletonModel
|
|
30
35
|
from slm.utils import get_exif_tags
|
|
31
36
|
|
|
32
37
|
|
|
38
|
+
def site_upload_path(instance: "SiteFile", filename: str) -> str:
|
|
39
|
+
"""
|
|
40
|
+
file will be saved to:
|
|
41
|
+
MEDIA_ROOT/uploads/<site name>/filename
|
|
42
|
+
|
|
43
|
+
:param filename: The name of the file
|
|
44
|
+
:return: The path where the site file should reside.
|
|
45
|
+
"""
|
|
46
|
+
from .index import ArchivedSiteLog
|
|
47
|
+
|
|
48
|
+
prefix = Path()
|
|
49
|
+
if instance.SUB_DIRECTORY:
|
|
50
|
+
prefix = Path(instance.SUB_DIRECTORY)
|
|
51
|
+
dest = prefix / instance.site.name / filename
|
|
52
|
+
timestamp = (
|
|
53
|
+
instance.index.begin
|
|
54
|
+
if isinstance(instance, ArchivedSiteLog)
|
|
55
|
+
else instance.timestamp
|
|
56
|
+
)
|
|
57
|
+
if (Path(settings.MEDIA_ROOT) / dest).exists():
|
|
58
|
+
stem, suffix = dest.stem, dest.suffix
|
|
59
|
+
dest = dest.with_name(f"{stem}_{timestamp.strftime('%H%M%S')}{suffix}")
|
|
60
|
+
return dest.as_posix()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def site_thumbnail_path(instance: "SiteFile", filename: str) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Return the path for the thumbnail image for the given filename.
|
|
66
|
+
|
|
67
|
+
:param filename: The name of the file
|
|
68
|
+
:return: The path where the thumbnail image should reside.
|
|
69
|
+
"""
|
|
70
|
+
path = Path(instance.upload_path(filename))
|
|
71
|
+
return (path.parent / "thumbnails" / path.name).as_posix()
|
|
72
|
+
|
|
73
|
+
|
|
33
74
|
class AgencyManager(models.Manager):
|
|
34
75
|
pass
|
|
35
76
|
|
|
@@ -144,33 +185,6 @@ class Network(models.Model):
|
|
|
144
185
|
return self.name
|
|
145
186
|
|
|
146
187
|
|
|
147
|
-
def site_upload_path(instance, filename):
|
|
148
|
-
"""
|
|
149
|
-
file will be saved to:
|
|
150
|
-
MEDIA_ROOT/uploads/<site name>/filename
|
|
151
|
-
|
|
152
|
-
:param instance: The SiteFile instance
|
|
153
|
-
:param filename: The name of the file
|
|
154
|
-
:return: The path where the site file should reside.
|
|
155
|
-
"""
|
|
156
|
-
prefix = ""
|
|
157
|
-
if instance.SUB_DIRECTORY:
|
|
158
|
-
prefix = f"{instance.SUB_DIRECTORY}/"
|
|
159
|
-
return f"{prefix}{instance.site.name}/{filename}"
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def site_thumbnail_path(instance, filename):
|
|
163
|
-
"""
|
|
164
|
-
Return the path for the thumbnail image for the given filename.
|
|
165
|
-
|
|
166
|
-
:param instance: The SiteFile instance
|
|
167
|
-
:param filename: The name of the file
|
|
168
|
-
:return: The path where the thumbnail image should reside.
|
|
169
|
-
"""
|
|
170
|
-
parts = str(site_upload_path(instance, filename)).split("/")
|
|
171
|
-
return "/".join([*parts[0:-1], "thumbnails", parts[-1]])
|
|
172
|
-
|
|
173
|
-
|
|
174
188
|
class SiteFile(models.Model):
|
|
175
189
|
SUB_DIRECTORY = "misc"
|
|
176
190
|
|
|
@@ -185,7 +199,9 @@ class SiteFile(models.Model):
|
|
|
185
199
|
)
|
|
186
200
|
|
|
187
201
|
timestamp = models.DateTimeField(
|
|
188
|
-
auto_now_add=True,
|
|
202
|
+
auto_now_add=True,
|
|
203
|
+
db_index=True,
|
|
204
|
+
help_text=_("When the file was created or uploaded."),
|
|
189
205
|
)
|
|
190
206
|
|
|
191
207
|
file = models.FileField(
|
|
@@ -193,6 +209,7 @@ class SiteFile(models.Model):
|
|
|
193
209
|
null=False,
|
|
194
210
|
max_length=255,
|
|
195
211
|
help_text=_("A pointer to the uploaded file on disk."),
|
|
212
|
+
unique=True,
|
|
196
213
|
)
|
|
197
214
|
|
|
198
215
|
size = models.PositiveIntegerField(null=True, default=None, blank=True)
|
|
@@ -480,7 +497,7 @@ class SiteFileUpload(SiteFile):
|
|
|
480
497
|
blank=True,
|
|
481
498
|
db_index=True,
|
|
482
499
|
help_text=_(
|
|
483
|
-
"The status of the file. This will also depend on what type the
|
|
500
|
+
"The status of the file. This will also depend on what type the file is."
|
|
484
501
|
),
|
|
485
502
|
)
|
|
486
503
|
|
|
@@ -655,9 +672,9 @@ class LogEntry(PolymorphicModel):
|
|
|
655
672
|
def __str__(self):
|
|
656
673
|
return (
|
|
657
674
|
f"({self.site.name} | "
|
|
658
|
-
f
|
|
675
|
+
f"{self.user.name or self.user.email if self.user else ''}) "
|
|
659
676
|
f"[{self.timestamp}]: {self.type} -> "
|
|
660
|
-
f
|
|
677
|
+
f"{self.section or self.file or self.site or ''}"
|
|
661
678
|
)
|
|
662
679
|
|
|
663
680
|
class Meta:
|
|
@@ -721,3 +738,27 @@ class SiteTideGauge(models.Model):
|
|
|
721
738
|
|
|
722
739
|
class Meta:
|
|
723
740
|
ordering = ("site", "distance")
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
class SLMVersion(SingletonModel):
|
|
744
|
+
"""
|
|
745
|
+
We store the SLM code version in the database to enable safe upgrades.
|
|
746
|
+
"""
|
|
747
|
+
|
|
748
|
+
version_str = models.CharField(default="", blank=True)
|
|
749
|
+
|
|
750
|
+
@classmethod
|
|
751
|
+
def update(cls, version: t.Optional[Version] = None):
|
|
752
|
+
if not version:
|
|
753
|
+
from slm import __version__ as slm_version
|
|
754
|
+
|
|
755
|
+
version = parse_version(slm_version)
|
|
756
|
+
instance = cls.load()
|
|
757
|
+
instance.version_str = str(version)
|
|
758
|
+
instance.save()
|
|
759
|
+
|
|
760
|
+
@property
|
|
761
|
+
def version(self) -> t.Optional[Version]:
|
|
762
|
+
if self.version_str:
|
|
763
|
+
return parse_version(self.version_str)
|
|
764
|
+
return None
|
slm/models/user.py
CHANGED
|
@@ -285,7 +285,7 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|
|
285
285
|
|
|
286
286
|
@property
|
|
287
287
|
def full_name(self):
|
|
288
|
-
return f
|
|
288
|
+
return f"{self.first_name or ''} {self.last_name or ''}".strip()
|
|
289
289
|
|
|
290
290
|
def __str__(self):
|
|
291
291
|
if self.name:
|
slm/parsing/__init__.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import re
|
|
2
|
+
import typing as t
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from datetime import date, datetime, timezone
|
|
4
5
|
from functools import partial
|
|
@@ -59,7 +60,8 @@ def remove_from_start(value: str, prefixes: List[str]):
|
|
|
59
60
|
|
|
60
61
|
@dataclass
|
|
61
62
|
class _Ignored:
|
|
62
|
-
msg: str
|
|
63
|
+
msg: str = ""
|
|
64
|
+
columns: t.Optional[t.Tuple[int, int]] = None
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
@dataclass
|
|
@@ -86,10 +88,24 @@ class Finding:
|
|
|
86
88
|
A base class for parser/binding findings.
|
|
87
89
|
"""
|
|
88
90
|
|
|
91
|
+
lineno: int
|
|
92
|
+
parser: "BaseParser"
|
|
93
|
+
message: str
|
|
94
|
+
section: t.Optional["BaseSection"]
|
|
95
|
+
parameter: t.Optional["BaseParameter"]
|
|
96
|
+
line: t.Optional[str]
|
|
89
97
|
priority: int = 0
|
|
98
|
+
columns: t.Optional[t.Tuple[int, int]]
|
|
90
99
|
|
|
91
100
|
def __init__(
|
|
92
|
-
self,
|
|
101
|
+
self,
|
|
102
|
+
lineno,
|
|
103
|
+
parser,
|
|
104
|
+
message,
|
|
105
|
+
section=None,
|
|
106
|
+
parameter=None,
|
|
107
|
+
line=None,
|
|
108
|
+
columns=None,
|
|
93
109
|
):
|
|
94
110
|
self.lineno = lineno
|
|
95
111
|
self.parser = parser
|
|
@@ -97,11 +113,12 @@ class Finding:
|
|
|
97
113
|
self.section = section
|
|
98
114
|
self.parameter = parameter
|
|
99
115
|
self.line = line
|
|
116
|
+
self.columns = columns
|
|
100
117
|
|
|
101
118
|
def __str__(self):
|
|
102
119
|
return (
|
|
103
|
-
f"({self.lineno+1: 4}) {self.parser.lines[self.lineno]}"
|
|
104
|
-
f'
|
|
120
|
+
f"({self.lineno + 1: 4}) {self.parser.lines[self.lineno]}"
|
|
121
|
+
f"{' ' * (80 - len(self.parser.lines[self.lineno]))}"
|
|
105
122
|
f"[{self.level.upper()}]: {self.message}"
|
|
106
123
|
)
|
|
107
124
|
|
|
@@ -265,7 +282,7 @@ class BaseSection:
|
|
|
265
282
|
section_str = f"{self.index_string} {self.header}\n"
|
|
266
283
|
if self.example:
|
|
267
284
|
section_str = f"{self.index_string} {self.header} (EXAMPLE)\n"
|
|
268
|
-
for
|
|
285
|
+
for param in self.parameters.values():
|
|
269
286
|
section_str += f"\t{param}\n"
|
|
270
287
|
return section_str
|
|
271
288
|
|
|
@@ -275,7 +292,7 @@ class BaseSection:
|
|
|
275
292
|
True if any parameter in this section contains a real value that is
|
|
276
293
|
not a placeholder.
|
|
277
294
|
"""
|
|
278
|
-
for
|
|
295
|
+
for param in self.parameters.values():
|
|
279
296
|
if not (param.is_empty or param.is_placeholder):
|
|
280
297
|
return True
|
|
281
298
|
return False
|
|
@@ -290,7 +307,7 @@ class BaseSection:
|
|
|
290
307
|
else:
|
|
291
308
|
index += "x"
|
|
292
309
|
if self.subsection_number and (self.order or self.example):
|
|
293
|
-
index += f
|
|
310
|
+
index += f".{self.order if self.order else 'x'}"
|
|
294
311
|
return index
|
|
295
312
|
|
|
296
313
|
@property
|
|
@@ -350,7 +367,7 @@ class BaseParser:
|
|
|
350
367
|
@property
|
|
351
368
|
def findings_context(self) -> Dict[int, Tuple[str, str]]:
|
|
352
369
|
return {
|
|
353
|
-
int(line): (finding.level, finding.message)
|
|
370
|
+
int(line): (finding.level, finding.message, finding.columns)
|
|
354
371
|
for line, finding in self.findings.items()
|
|
355
372
|
}
|
|
356
373
|
|
|
@@ -476,7 +493,7 @@ def to_antenna(value):
|
|
|
476
493
|
except Antenna.DoesNotExist:
|
|
477
494
|
antennas = "\n".join([ant.model for ant in Antenna.objects.public()])
|
|
478
495
|
raise ValueError(
|
|
479
|
-
f"Unexpected antenna model {antenna}. Must be one of
|
|
496
|
+
f"Unexpected antenna model {antenna}. Must be one of \n{antennas}"
|
|
480
497
|
)
|
|
481
498
|
|
|
482
499
|
|
|
@@ -487,7 +504,7 @@ def to_radome(value):
|
|
|
487
504
|
except Radome.DoesNotExist:
|
|
488
505
|
radomes = "\n".join([rad.model for rad in Radome.objects.public()])
|
|
489
506
|
raise ValueError(
|
|
490
|
-
f"Unexpected radome model {radome}. Must be one of
|
|
507
|
+
f"Unexpected radome model {radome}. Must be one of \n{radomes}"
|
|
491
508
|
)
|
|
492
509
|
|
|
493
510
|
|
|
@@ -498,7 +515,7 @@ def to_receiver(value):
|
|
|
498
515
|
except Receiver.DoesNotExist:
|
|
499
516
|
receivers = "\n".join([rec.model for rec in Receiver.objects.public()])
|
|
500
517
|
raise ValueError(
|
|
501
|
-
f"Unexpected receiver model {receiver}. Must be one of
|
|
518
|
+
f"Unexpected receiver model {receiver}. Must be one of \n{receivers}"
|
|
502
519
|
)
|
|
503
520
|
|
|
504
521
|
|
|
@@ -546,7 +563,7 @@ def to_enum(enum_cls, value, strict=False, blank=None, ignored=None):
|
|
|
546
563
|
return value.strip()
|
|
547
564
|
|
|
548
565
|
valid_list = " \n".join(en.label for en in enum_cls)
|
|
549
|
-
raise ValueError(f"Invalid value {value} must be one of:\n
|
|
566
|
+
raise ValueError(f"Invalid value {value} must be one of:\n{valid_list}")
|
|
550
567
|
return blank
|
|
551
568
|
|
|
552
569
|
|
|
@@ -665,7 +682,7 @@ def to_decimal_degrees(value):
|
|
|
665
682
|
flt = to_float(value)
|
|
666
683
|
if isinstance(flt, _Warning):
|
|
667
684
|
return _Warning(msg=flt.msg, value=dddmmssss_to_decimal(flt.value))
|
|
668
|
-
if
|
|
685
|
+
if isinstance(flt, _Ignored):
|
|
669
686
|
return flt
|
|
670
687
|
return dddmmssss_to_decimal(to_float(value))
|
|
671
688
|
|
|
@@ -718,12 +735,12 @@ def to_pressure(value):
|
|
|
718
735
|
def _to_date(value):
|
|
719
736
|
if value.strip():
|
|
720
737
|
if "CCYY-MM-DD" in value.upper():
|
|
721
|
-
return _Ignored
|
|
738
|
+
return _Ignored(msg=f"{value} is a placeholder.")
|
|
722
739
|
try:
|
|
723
740
|
return parse_date(value, tzinfos=TZ_INFOS).date()
|
|
724
741
|
except Exception as exc:
|
|
725
742
|
raise ValueError(
|
|
726
|
-
f"Unable to parse {value} into a date. Expected
|
|
743
|
+
f"Unable to parse {value} into a date. Expected format: CCYY-MM-DD"
|
|
727
744
|
) from exc
|
|
728
745
|
return None
|
|
729
746
|
|
|
@@ -731,7 +748,7 @@ def _to_date(value):
|
|
|
731
748
|
def _to_datetime(value):
|
|
732
749
|
if value.strip():
|
|
733
750
|
if "CCYY-MM-DD" in value.upper():
|
|
734
|
-
return _Ignored
|
|
751
|
+
return _Ignored(msg=f"{value} is a placeholder.")
|
|
735
752
|
try:
|
|
736
753
|
# UT and UTUT has been seen as a timezone specifier in the wild
|
|
737
754
|
dt = parse_date(value, tzinfos=TZ_INFOS)
|
slm/parsing/legacy/binding.py
CHANGED
|
@@ -71,7 +71,7 @@ def reg(name, header_index, bindings):
|
|
|
71
71
|
def ignored(_, msg=""):
|
|
72
72
|
if msg:
|
|
73
73
|
return _Ignored(msg)
|
|
74
|
-
return _Ignored
|
|
74
|
+
return _Ignored()
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
def to_temp_stab(value):
|
|
@@ -129,40 +129,33 @@ def to_temp_stab(value):
|
|
|
129
129
|
return stabilized, nominal, deviation
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
def
|
|
132
|
+
def effective_date(value: str, part: int, label: str):
|
|
133
133
|
try:
|
|
134
|
-
|
|
134
|
+
dt_str = ""
|
|
135
135
|
if value.strip():
|
|
136
136
|
sep = "/" if "/" in value else " - "
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
splt = value.split(sep)
|
|
138
|
+
if len(splt) > part:
|
|
139
|
+
dt_str = splt[part]
|
|
140
|
+
if dt_str.strip():
|
|
141
|
+
ret = to_date(dt_str.strip())
|
|
142
|
+
if isinstance(ret, _Ignored):
|
|
143
|
+
start = value.index(dt_str)
|
|
144
|
+
end = start + len(dt_str)
|
|
145
|
+
ret.columns = (start, end)
|
|
146
|
+
return ret
|
|
139
147
|
return None
|
|
140
148
|
except ValueError as ve:
|
|
141
|
-
if
|
|
149
|
+
if dt_str.upper() in DATE_PLACEHOLDERS:
|
|
142
150
|
return None
|
|
143
151
|
raise ValueError(
|
|
144
|
-
f"Unable to parse {value} into an expected
|
|
152
|
+
f"Unable to parse {value} into an expected {label} date. Expected "
|
|
145
153
|
f"format: CCYY-MM-DD/CCYY-MM-DD"
|
|
146
154
|
) from ve
|
|
147
155
|
|
|
148
156
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
end_str = ""
|
|
152
|
-
if value.strip():
|
|
153
|
-
sep = "/" if "/" in value else " - "
|
|
154
|
-
splt = value.split(sep)
|
|
155
|
-
if len(splt) > 1:
|
|
156
|
-
end_str = value.split(sep)[1]
|
|
157
|
-
return to_date(end_str)
|
|
158
|
-
return None
|
|
159
|
-
except ValueError as ve:
|
|
160
|
-
if end_str.upper() in DATE_PLACEHOLDERS:
|
|
161
|
-
return None
|
|
162
|
-
raise ValueError(
|
|
163
|
-
f"Unable to parse {value} into an expected end date. Expected "
|
|
164
|
-
f"format: CCYY-MM-DD/CCYY-MM-DD"
|
|
165
|
-
) from ve
|
|
157
|
+
effective_start = partial(effective_date, part=0, label="start")
|
|
158
|
+
effective_end = partial(effective_date, part=1, label="end")
|
|
166
159
|
|
|
167
160
|
|
|
168
161
|
def no_sat_warning(line_no, parser, satellites):
|
|
@@ -462,12 +455,12 @@ class SiteLogBinder(BaseBinder):
|
|
|
462
455
|
("Tied Marker Usage", ("usage", to_str)),
|
|
463
456
|
("Tied Marker CDP Number", ("cdp_number", to_str)),
|
|
464
457
|
("Tied Marker DOMES Number", ("domes_number", to_str)),
|
|
465
|
-
("dx", ("dx", to_float)),
|
|
466
|
-
("dy", ("dy", to_float)),
|
|
467
|
-
("dz", ("dz", to_float)),
|
|
468
|
-
("dx (m)", ("dx", to_float)),
|
|
469
|
-
("dy (m)", ("dy", to_float)),
|
|
470
|
-
("dz (m)", ("dz", to_float)),
|
|
458
|
+
("dx", ("dx", partial(to_float, units=["m"]))),
|
|
459
|
+
("dy", ("dy", partial(to_float, units=["m"]))),
|
|
460
|
+
("dz", ("dz", partial(to_float, units=["m"]))),
|
|
461
|
+
("dx (m)", ("dx", partial(to_float, units=["m"]))),
|
|
462
|
+
("dy (m)", ("dy", partial(to_float, units=["m"]))),
|
|
463
|
+
("dz (m)", ("dz", partial(to_float, units=["m"]))),
|
|
471
464
|
(
|
|
472
465
|
"Accuracy",
|
|
473
466
|
(
|
|
@@ -656,13 +649,43 @@ class SiteLogBinder(BaseBinder):
|
|
|
656
649
|
},
|
|
657
650
|
}
|
|
658
651
|
|
|
652
|
+
MULTIPLE_ENTRIES = {
|
|
653
|
+
3,
|
|
654
|
+
4,
|
|
655
|
+
5,
|
|
656
|
+
6,
|
|
657
|
+
7,
|
|
658
|
+
(8, 1),
|
|
659
|
+
(8, 2),
|
|
660
|
+
(8, 3),
|
|
661
|
+
(8, 4),
|
|
662
|
+
(8, 5),
|
|
663
|
+
(9, 1),
|
|
664
|
+
(9, 2),
|
|
665
|
+
(9, 3),
|
|
666
|
+
10,
|
|
667
|
+
}
|
|
668
|
+
|
|
659
669
|
def __init__(self, parsed: SiteLogParser):
|
|
660
670
|
super().__init__(parsed)
|
|
661
|
-
for
|
|
662
|
-
if section.example
|
|
671
|
+
for section in self.parsed.sections.values():
|
|
672
|
+
if section.example:
|
|
673
|
+
continue
|
|
674
|
+
section_depth = sum(1 for item in section.index_tuple if item is not None)
|
|
675
|
+
heading_depth = (
|
|
676
|
+
1
|
|
677
|
+
if not isinstance(section.heading_index, tuple)
|
|
678
|
+
else len(section.heading_index)
|
|
679
|
+
)
|
|
680
|
+
is_header = (
|
|
681
|
+
section.heading_index in self.MULTIPLE_ENTRIES
|
|
682
|
+
and section_depth == heading_depth
|
|
683
|
+
)
|
|
684
|
+
if is_header and not section.contains_values:
|
|
685
|
+
# skip empty headers
|
|
663
686
|
continue
|
|
664
687
|
if section.heading_index not in self.TRANSLATION_TABLE:
|
|
665
|
-
for
|
|
688
|
+
for _ in range(section.line_no, section.line_end):
|
|
666
689
|
self.parsed.add_finding(
|
|
667
690
|
Warn(
|
|
668
691
|
section.line_no,
|
|
@@ -738,16 +761,24 @@ class SiteLogBinder(BaseBinder):
|
|
|
738
761
|
if parameter.is_placeholder
|
|
739
762
|
else parse(parameter.value)
|
|
740
763
|
)
|
|
741
|
-
if
|
|
764
|
+
if isinstance(value, _Ignored):
|
|
765
|
+
cols = None
|
|
766
|
+
if cols := getattr(value, "columns", None):
|
|
767
|
+
val_start = self.lines[parameter.line_no].index(
|
|
768
|
+
parameter.value
|
|
769
|
+
)
|
|
770
|
+
cols = (cols[0] + val_start, cols[1] + val_start)
|
|
742
771
|
self.parsed.add_finding(
|
|
743
772
|
Ignored(
|
|
744
773
|
parameter.line_no,
|
|
745
774
|
self.parsed,
|
|
746
775
|
getattr(value, "msg", _("Parameter is ignored")),
|
|
747
776
|
section=section,
|
|
777
|
+
columns=cols,
|
|
748
778
|
)
|
|
749
779
|
)
|
|
750
780
|
ignored.add(param)
|
|
781
|
+
parameter.bind(param, None)
|
|
751
782
|
elif isinstance(value, _Warning):
|
|
752
783
|
self.parsed.add_finding(
|
|
753
784
|
Warn(
|
slm/parsing/legacy/parser.py
CHANGED
|
@@ -52,8 +52,8 @@ class ParsedParameter(BaseParameter):
|
|
|
52
52
|
super().__init__(
|
|
53
53
|
line_no=line_no,
|
|
54
54
|
name=(
|
|
55
|
-
f
|
|
56
|
-
f'
|
|
55
|
+
f"{sub_heading if sub_heading else ''}"
|
|
56
|
+
f"{'::' if sub_heading else ''}"
|
|
57
57
|
f"{match.group(1).strip()}"
|
|
58
58
|
),
|
|
59
59
|
values=[match.group(2).strip()],
|
slm/parsing/xsd/binding.py
CHANGED
|
@@ -36,7 +36,7 @@ class SiteLogBinder(BaseBinder):
|
|
|
36
36
|
TRANSLATION_TABLE[GeodesyMLVersion.v0_5] = {
|
|
37
37
|
(
|
|
38
38
|
(0,),
|
|
39
|
-
"/geo:GeodesyML/geo:siteLog/geo:formInformation/
|
|
39
|
+
"/geo:GeodesyML/geo:siteLog/geo:formInformation/geo:FormInformation",
|
|
40
40
|
): TRANSLATION_TABLE[GeodesyMLVersion.v0_4][
|
|
41
41
|
((0,), "/geo:GeodesyML/geo:siteLog/geo:formInformation")
|
|
42
42
|
],
|
slm/parsing/xsd/parser.py
CHANGED
slm/receivers/__init__.py
CHANGED
|
@@ -6,6 +6,6 @@ def register():
|
|
|
6
6
|
if not _registered:
|
|
7
7
|
# the order of these imports is important, index receivers must happen
|
|
8
8
|
# before alert receivers
|
|
9
|
-
from slm.receivers import alerts, cleanup, event_loggers, index
|
|
9
|
+
from slm.receivers import alerts, cleanup, event_loggers, index, migration
|
|
10
10
|
|
|
11
|
-
_registered = event_loggers and cleanup and index and alerts
|
|
11
|
+
_registered = event_loggers and cleanup and index and alerts and migration
|
slm/receivers/index.py
CHANGED
|
@@ -5,7 +5,7 @@ from slm.defines import SiteLogStatus
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
@receiver(slm_signals.site_status_changed)
|
|
8
|
-
def index_site(sender, site, previous_status, new_status, **kwargs):
|
|
8
|
+
def index_site(sender, site, previous_status, new_status, reverted=False, **kwargs):
|
|
9
9
|
from slm.models import ArchiveIndex
|
|
10
10
|
|
|
11
11
|
if site.last_publish and (
|
|
@@ -18,6 +18,7 @@ def index_site(sender, site, previous_status, new_status, **kwargs):
|
|
|
18
18
|
and previous_status in SiteLogStatus.active_states()
|
|
19
19
|
and previous_status is not SiteLogStatus.PUBLISHED
|
|
20
20
|
and new_status is SiteLogStatus.PUBLISHED
|
|
21
|
+
and not reverted
|
|
21
22
|
):
|
|
22
23
|
# catch an edge case where a section publish triggers a whole log publish
|
|
23
24
|
# these signals/edit state diagram needs to be cleaned up. this code is
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
We update our SLMVersion tag when migrate is run - which should happen everytime the
|
|
3
|
+
SLM software is updated.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from django.db.models.signals import post_migrate, pre_migrate
|
|
7
|
+
from django.dispatch import receiver
|
|
8
|
+
|
|
9
|
+
from slm.models import SLMVersion
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@receiver(pre_migrate)
|
|
13
|
+
def check_safe_upgrade(**_):
|
|
14
|
+
from slm.management.commands.check_upgrade import Command as CheckUpgrade
|
|
15
|
+
|
|
16
|
+
CheckUpgrade()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@receiver(post_migrate)
|
|
20
|
+
def update_slm_version(**_):
|
|
21
|
+
SLMVersion.update()
|