igs-slm 0.1.5b2__py3-none-any.whl → 0.2.0b0__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.5b2.dist-info → igs_slm-0.2.0b0.dist-info}/METADATA +2 -2
- {igs_slm-0.1.5b2.dist-info → igs_slm-0.2.0b0.dist-info}/RECORD +47 -34
- slm/__init__.py +1 -1
- slm/admin.py +40 -3
- slm/api/edit/views.py +37 -2
- slm/api/public/serializers.py +1 -1
- slm/defines/CoordinateMode.py +9 -0
- slm/defines/SiteLogFormat.py +19 -6
- slm/defines/__init__.py +24 -22
- slm/file_views/apps.py +7 -0
- slm/file_views/config.py +253 -0
- slm/file_views/settings.py +124 -0
- slm/file_views/static/slm/file_views/banner_header.png +0 -0
- slm/file_views/static/slm/file_views/css/listing.css +82 -0
- slm/file_views/templates/slm/file_views/listing.html +70 -0
- slm/file_views/urls.py +47 -0
- slm/file_views/views.py +472 -0
- slm/forms.py +22 -4
- slm/jinja2/slm/sitelog/ascii_9char.log +1 -1
- slm/jinja2/slm/sitelog/legacy.log +1 -1
- slm/management/commands/check_upgrade.py +11 -11
- slm/management/commands/generate_sinex.py +9 -7
- slm/map/settings.py +0 -0
- slm/migrations/0001_alter_archivedsitelog_size_and_more.py +44 -0
- slm/migrations/0032_archiveindex_valid_range_and_more.py +8 -1
- slm/migrations/simplify_daily_index_files.py +86 -0
- slm/models/index.py +73 -6
- slm/models/sitelog.py +6 -0
- slm/models/system.py +35 -2
- slm/parsing/__init__.py +10 -0
- slm/parsing/legacy/binding.py +3 -2
- slm/receivers/cache.py +25 -0
- slm/settings/root.py +22 -0
- slm/settings/routines.py +1 -0
- slm/settings/slm.py +71 -10
- slm/settings/urls.py +1 -1
- slm/settings/validation.py +5 -4
- slm/signals.py +33 -23
- slm/static/slm/js/enums.js +7 -6
- slm/static/slm/js/form.js +25 -14
- slm/static/slm/js/slm.js +4 -2
- slm/templatetags/slm.py +3 -3
- slm/utils.py +161 -36
- slm/validators.py +51 -0
- {igs_slm-0.1.5b2.dist-info → igs_slm-0.2.0b0.dist-info}/WHEEL +0 -0
- {igs_slm-0.1.5b2.dist-info → igs_slm-0.2.0b0.dist-info}/entry_points.txt +0 -0
- {igs_slm-0.1.5b2.dist-info → igs_slm-0.2.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# Generated by Django 4.2.23 on 2025-09-03 23:05
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
dependencies = [
|
8
|
+
("slm", "simplify_daily_index_files"),
|
9
|
+
]
|
10
|
+
|
11
|
+
operations = [
|
12
|
+
migrations.AlterField(
|
13
|
+
model_name="archivedsitelog",
|
14
|
+
name="size",
|
15
|
+
field=models.PositiveIntegerField(
|
16
|
+
blank=True, db_index=True, default=None, null=True
|
17
|
+
),
|
18
|
+
),
|
19
|
+
migrations.AlterField(
|
20
|
+
model_name="geodesymlinvalid",
|
21
|
+
name="size",
|
22
|
+
field=models.PositiveIntegerField(
|
23
|
+
blank=True, db_index=True, default=None, null=True
|
24
|
+
),
|
25
|
+
),
|
26
|
+
migrations.AlterField(
|
27
|
+
model_name="sitefileupload",
|
28
|
+
name="size",
|
29
|
+
field=models.PositiveIntegerField(
|
30
|
+
blank=True, db_index=True, default=None, null=True
|
31
|
+
),
|
32
|
+
),
|
33
|
+
migrations.AlterField(
|
34
|
+
model_name="sitemoreinformation",
|
35
|
+
name="more_info",
|
36
|
+
field=models.URLField(
|
37
|
+
blank=True,
|
38
|
+
db_index=True,
|
39
|
+
default="",
|
40
|
+
max_length=8000,
|
41
|
+
verbose_name="URL for More Information",
|
42
|
+
),
|
43
|
+
),
|
44
|
+
]
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# Generated by Django 4.2.20 on 2025-05-08 19:52
|
2
2
|
|
3
|
+
from datetime import timedelta
|
4
|
+
|
3
5
|
import django.contrib.postgres.constraints
|
4
6
|
import django.contrib.postgres.fields.ranges
|
5
7
|
from django.contrib.postgres.fields.ranges import DateTimeTZRange
|
@@ -10,8 +12,13 @@ def populate_valid_range(apps, schema_editor):
|
|
10
12
|
ArchiveIndex = apps.get_model("slm", "ArchiveIndex")
|
11
13
|
|
12
14
|
for row in ArchiveIndex.objects.all():
|
15
|
+
begin = row.begin
|
16
|
+
end = row.end
|
17
|
+
if begin == end:
|
18
|
+
# add a little salt
|
19
|
+
end = end + timedelta(seconds=1)
|
13
20
|
ArchiveIndex.objects.filter(pk=row.pk).update(
|
14
|
-
valid_range=DateTimeTZRange(
|
21
|
+
valid_range=DateTimeTZRange(begin, end)
|
15
22
|
)
|
16
23
|
|
17
24
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
"""
|
2
|
+
If multiple index files exist for a given station on a given day we remove the timestamp
|
3
|
+
from the name of the latest daily file and add the timestamp to the earliest daily file.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from datetime import datetime, time, timedelta, timezone
|
7
|
+
from pathlib import Path
|
8
|
+
|
9
|
+
from django.db import migrations
|
10
|
+
from django.db.models import Count, Value
|
11
|
+
from django.db.models.functions import Length, Replace
|
12
|
+
|
13
|
+
|
14
|
+
def simplify_index(apps, schema_editor):
|
15
|
+
ArchivedSiteLog = apps.get_model("slm", "ArchivedSiteLog")
|
16
|
+
ArchiveIndex = apps.get_model("slm", "ArchiveIndex")
|
17
|
+
|
18
|
+
multi_day_indexes = ArchiveIndex.objects.filter(
|
19
|
+
files__in=ArchivedSiteLog.objects.annotate(
|
20
|
+
ucount=Length("file") - Length(Replace("file", Value("_"), Value("")))
|
21
|
+
).filter(ucount__gte=2)
|
22
|
+
).distinct()
|
23
|
+
|
24
|
+
multi_day_tuples = list(
|
25
|
+
multi_day_indexes.extra(select={"range_date": "DATE(lower(valid_range))"})
|
26
|
+
.values("site__name", "range_date")
|
27
|
+
.annotate(count=Count("id"))
|
28
|
+
.filter(count__gt=1)
|
29
|
+
.values_list("site__name", "range_date", flat=False)
|
30
|
+
)
|
31
|
+
|
32
|
+
for station, day in multi_day_tuples:
|
33
|
+
lower = datetime.combine(day, time.min, tzinfo=timezone.utc)
|
34
|
+
upper = lower + timedelta(days=1)
|
35
|
+
indexes = ArchiveIndex.objects.filter(
|
36
|
+
site__name=station,
|
37
|
+
valid_range__startswith__gte=lower,
|
38
|
+
valid_range__startswith__lte=upper,
|
39
|
+
).order_by("valid_range")
|
40
|
+
assert indexes.count() >= 2
|
41
|
+
first = indexes.first()
|
42
|
+
last = indexes.last()
|
43
|
+
|
44
|
+
for file in first.files.all():
|
45
|
+
current_path = Path(file.file.path)
|
46
|
+
new_path = Path(file.file.path)
|
47
|
+
if current_path.stem.count("_") < 2:
|
48
|
+
new_path = current_path.with_name(
|
49
|
+
f"{current_path.stem}_{first.valid_range.lower.strftime('%H%M%S')}{current_path.suffix}"
|
50
|
+
)
|
51
|
+
file.file.name = file.file.name.replace(
|
52
|
+
current_path.name, new_path.name
|
53
|
+
)
|
54
|
+
file.name = Path(file.file.name).name
|
55
|
+
current_path.rename(new_path)
|
56
|
+
file.save(update_fields=["file", "name"])
|
57
|
+
print(f"Updated {current_path.name} -> {new_path.name}")
|
58
|
+
|
59
|
+
for file in last.files.all():
|
60
|
+
current_path = Path(file.file.path)
|
61
|
+
new_path = Path(file.file.path)
|
62
|
+
if current_path.stem.count("_") >= 2:
|
63
|
+
new_path = current_path.with_name(
|
64
|
+
"_".join(current_path.stem.split("_")[:-1]) + current_path.suffix
|
65
|
+
)
|
66
|
+
file.file.name = file.file.name.replace(
|
67
|
+
current_path.name, new_path.name
|
68
|
+
)
|
69
|
+
file.name = Path(file.file.name).name
|
70
|
+
current_path.rename(new_path)
|
71
|
+
file.save(update_fields=["file", "name"])
|
72
|
+
print(f"Updated {current_path.name} -> {new_path.name}")
|
73
|
+
|
74
|
+
|
75
|
+
def noop(apps, schema_editor):
|
76
|
+
pass
|
77
|
+
|
78
|
+
|
79
|
+
class Migration(migrations.Migration):
|
80
|
+
dependencies = [
|
81
|
+
("slm", "0005_slmversion"),
|
82
|
+
]
|
83
|
+
|
84
|
+
operations = [
|
85
|
+
migrations.RunPython(simplify_index, reverse_code=noop, atomic=True),
|
86
|
+
]
|
slm/models/index.py
CHANGED
@@ -2,6 +2,7 @@ import os
|
|
2
2
|
import typing as t
|
3
3
|
from datetime import datetime
|
4
4
|
|
5
|
+
from django.conf import settings
|
5
6
|
from django.contrib.postgres.constraints import ExclusionConstraint
|
6
7
|
from django.contrib.postgres.fields import DateTimeRangeField
|
7
8
|
from django.contrib.postgres.fields.ranges import DateTimeTZRange, RangeOperators
|
@@ -9,15 +10,19 @@ from django.core.exceptions import ValidationError
|
|
9
10
|
from django.core.files.base import ContentFile
|
10
11
|
from django.db import models, transaction
|
11
12
|
from django.db.models import (
|
13
|
+
Case,
|
12
14
|
CharField,
|
13
15
|
DateTimeField,
|
14
16
|
Deferrable,
|
15
17
|
F,
|
16
18
|
Func,
|
19
|
+
IntegerField,
|
17
20
|
OuterRef,
|
18
21
|
Q,
|
19
22
|
Subquery,
|
20
23
|
Value,
|
24
|
+
When,
|
25
|
+
Window,
|
21
26
|
)
|
22
27
|
from django.db.models.functions import (
|
23
28
|
Cast,
|
@@ -28,7 +33,9 @@ from django.db.models.functions import (
|
|
28
33
|
Lower,
|
29
34
|
LPad,
|
30
35
|
Now,
|
36
|
+
RowNumber,
|
31
37
|
Substr,
|
38
|
+
TruncDate,
|
32
39
|
)
|
33
40
|
from django.urls import reverse
|
34
41
|
from django.utils.functional import cached_property
|
@@ -274,7 +281,7 @@ class ArchiveIndexQuerySet(models.QuerySet):
|
|
274
281
|
name_len: t.Optional[int] = None,
|
275
282
|
field_name: str = "filename",
|
276
283
|
lower_case: bool = False,
|
277
|
-
log_format:
|
284
|
+
log_format: SiteLogFormat = None,
|
278
285
|
):
|
279
286
|
"""
|
280
287
|
Add the log names (w/o) extension as a property called filename to
|
@@ -308,7 +315,7 @@ class ArchiveIndexQuerySet(models.QuerySet):
|
|
308
315
|
LPad(Cast(ExtractDay(begin), models.CharField()), 2, fill_text=Value("0")),
|
309
316
|
]
|
310
317
|
if log_format:
|
311
|
-
parts.append(Value(f".{log_format.
|
318
|
+
parts.append(Value(f".{log_format.suffix}"))
|
312
319
|
|
313
320
|
return self.annotate(**{field_name: Concat(*parts, output_field=CharField())})
|
314
321
|
|
@@ -455,7 +462,7 @@ class ArchivedSiteLogQuerySet(models.QuerySet):
|
|
455
462
|
name_len: t.Optional[int] = None,
|
456
463
|
field_name: str = "filename",
|
457
464
|
lower_case: bool = False,
|
458
|
-
|
465
|
+
include_ext: bool = True,
|
459
466
|
):
|
460
467
|
"""
|
461
468
|
Add the log names (w/o) extension as a property called filename to
|
@@ -465,7 +472,7 @@ class ArchivedSiteLogQuerySet(models.QuerySet):
|
|
465
472
|
the first name_len characters of the site name.
|
466
473
|
:param field_name: Change the name of the annotated field.
|
467
474
|
:param lower_case: Filenames will be lowercase if true.
|
468
|
-
:param
|
475
|
+
:param include_ext: If true (default), include the extension for the log file type.
|
469
476
|
:return: A queryset with the filename annotation added.
|
470
477
|
"""
|
471
478
|
name_str = F("site__name")
|
@@ -496,10 +503,70 @@ class ArchivedSiteLogQuerySet(models.QuerySet):
|
|
496
503
|
fill_text=Value("0"),
|
497
504
|
),
|
498
505
|
]
|
499
|
-
if
|
500
|
-
parts.append(
|
506
|
+
if include_ext:
|
507
|
+
parts.append(
|
508
|
+
Case(
|
509
|
+
*[
|
510
|
+
When(log_format=key, then=Value(f".{ext}"))
|
511
|
+
for key, ext in getattr(
|
512
|
+
settings,
|
513
|
+
"SLM_FORMAT_EXTENSIONS",
|
514
|
+
{fmt: fmt.ext for fmt in SiteLogFormat},
|
515
|
+
).items()
|
516
|
+
],
|
517
|
+
default=Value(""),
|
518
|
+
output_field=CharField(),
|
519
|
+
),
|
520
|
+
)
|
501
521
|
return self.annotate(**{field_name: Concat(*parts)})
|
502
522
|
|
523
|
+
def best_format(self):
|
524
|
+
"""
|
525
|
+
This query fetches a linear history of site logs, but only picks the most appropriate
|
526
|
+
format from the index for each point in time. By default the most appropriate format
|
527
|
+
is the rank ordering defined in :class:`slm.defines.SiteLogFormat` unless otherwise
|
528
|
+
specified in the :setting:`SLM_FORMAT_PRIORITY` mapping.
|
529
|
+
"""
|
530
|
+
if priorities := getattr(settings, "SLM_FORMAT_PRIORITY"):
|
531
|
+
return self.annotate(
|
532
|
+
log_format_order=Case(
|
533
|
+
*[When(log_format=k, then=v) for k, v in priorities.items()],
|
534
|
+
default=999,
|
535
|
+
output_field=IntegerField(),
|
536
|
+
),
|
537
|
+
best_fmt=Window(
|
538
|
+
expression=RowNumber(),
|
539
|
+
partition_by=[
|
540
|
+
F("index__site"),
|
541
|
+
TruncDate("timestamp"),
|
542
|
+
],
|
543
|
+
order_by=[
|
544
|
+
F("timestamp").desc(),
|
545
|
+
F("log_format_order").asc(), # use mapped priority
|
546
|
+
],
|
547
|
+
),
|
548
|
+
).filter(best_fmt=1)
|
549
|
+
else:
|
550
|
+
return self.annotate(
|
551
|
+
best_fmt=Window(
|
552
|
+
expression=RowNumber(),
|
553
|
+
partition_by=[
|
554
|
+
F("index__site"),
|
555
|
+
TruncDate("timestamp"),
|
556
|
+
],
|
557
|
+
order_by=[
|
558
|
+
F("timestamp").desc(),
|
559
|
+
F("log_format").desc(),
|
560
|
+
], # higher format wins
|
561
|
+
),
|
562
|
+
).filter(best_fmt=1)
|
563
|
+
|
564
|
+
def most_recent(self):
|
565
|
+
return self.filter(index__valid_range__upper_inf=True)
|
566
|
+
|
567
|
+
def non_current(self):
|
568
|
+
return self.filter(index__valid_range__upper_inf=False)
|
569
|
+
|
503
570
|
|
504
571
|
class ArchivedSiteLog(SiteFile):
|
505
572
|
SUB_DIRECTORY = "archive"
|
slm/models/sitelog.py
CHANGED
@@ -45,6 +45,7 @@ from slm.defines import (
|
|
45
45
|
AntennaReferencePoint,
|
46
46
|
Aspiration,
|
47
47
|
CollocationStatus,
|
48
|
+
CoordinateMode,
|
48
49
|
FractureSpacing,
|
49
50
|
FrequencyStandardType,
|
50
51
|
ISOCountry,
|
@@ -2255,6 +2256,10 @@ class SiteLocation(SiteSection):
|
|
2255
2256
|
|
2256
2257
|
objects = SiteLocationManager.from_queryset(SiteLocationQueryset)()
|
2257
2258
|
|
2259
|
+
coordinate_mode = getattr(
|
2260
|
+
settings, "SLM_COORDINATE_MODE", CoordinateMode.INDEPENDENT
|
2261
|
+
)
|
2262
|
+
|
2258
2263
|
@classmethod
|
2259
2264
|
def structure(cls):
|
2260
2265
|
return [
|
@@ -4219,6 +4224,7 @@ class SiteMoreInformation(SiteSection):
|
|
4219
4224
|
blank=True,
|
4220
4225
|
verbose_name=_("URL for More Information"),
|
4221
4226
|
db_index=True,
|
4227
|
+
max_length=8000,
|
4222
4228
|
)
|
4223
4229
|
|
4224
4230
|
sitemap = models.CharField(
|
slm/models/system.py
CHANGED
@@ -40,6 +40,11 @@ def site_upload_path(instance: "SiteFile", filename: str) -> str:
|
|
40
40
|
file will be saved to:
|
41
41
|
MEDIA_ROOT/uploads/<site name>/filename
|
42
42
|
|
43
|
+
If there is a name collision with a file on disk, the _HHMMSS from the file's
|
44
|
+
timestamp will be added onto the name. If the collision is with an ArchivedSiteLog
|
45
|
+
the file already on disk will have its name changed to include the timestamp. This
|
46
|
+
keeps the most recently indexed file for each day having just the date timestamp.
|
47
|
+
|
43
48
|
:param filename: The name of the file
|
44
49
|
:return: The path where the site file should reside.
|
45
50
|
"""
|
@@ -56,7 +61,29 @@ def site_upload_path(instance: "SiteFile", filename: str) -> str:
|
|
56
61
|
)
|
57
62
|
if (Path(settings.MEDIA_ROOT) / dest).exists():
|
58
63
|
stem, suffix = dest.stem, dest.suffix
|
59
|
-
|
64
|
+
if isinstance(instance, ArchivedSiteLog):
|
65
|
+
# TODO - there is potential here for the database to be out of sync with the filesytem if the outer
|
66
|
+
# transaction fails
|
67
|
+
try:
|
68
|
+
for archive in ArchivedSiteLog.objects.filter(
|
69
|
+
file__endswith=str(dest.as_posix())
|
70
|
+
):
|
71
|
+
current_path = Path(archive.file.path)
|
72
|
+
new_path = Path(archive.file.path)
|
73
|
+
if current_path.stem.count("_") < 2:
|
74
|
+
new_path = current_path.with_name(
|
75
|
+
f"{current_path.stem}_{archive.index.valid_range.lower.strftime('%H%M%S')}{current_path.suffix}"
|
76
|
+
)
|
77
|
+
archive.file.name = archive.file.name.replace(
|
78
|
+
current_path.name, new_path.name
|
79
|
+
)
|
80
|
+
archive.name = Path(archive.file.name).name
|
81
|
+
current_path.rename(new_path)
|
82
|
+
archive.save(update_fields=["file", "name"])
|
83
|
+
except ArchivedSiteLog.DoesNotExist:
|
84
|
+
(Path(settings.MEDIA_ROOT) / dest).unlink()
|
85
|
+
else:
|
86
|
+
dest = dest.with_name(f"{stem}_{timestamp.strftime('%H%M%S')}{suffix}")
|
60
87
|
return dest.as_posix()
|
61
88
|
|
62
89
|
|
@@ -212,7 +239,9 @@ class SiteFile(models.Model):
|
|
212
239
|
unique=True,
|
213
240
|
)
|
214
241
|
|
215
|
-
size = models.PositiveIntegerField(
|
242
|
+
size = models.PositiveIntegerField(
|
243
|
+
null=True, default=None, blank=True, db_index=True
|
244
|
+
)
|
216
245
|
|
217
246
|
thumbnail = models.ImageField(
|
218
247
|
upload_to=site_thumbnail_path,
|
@@ -254,6 +283,10 @@ class SiteFile(models.Model):
|
|
254
283
|
help_text=_("The Geodesy ML version. (Only if file_type is GeodesyML)"),
|
255
284
|
)
|
256
285
|
|
286
|
+
@property
|
287
|
+
def on_disk(self) -> Path:
|
288
|
+
return Path(self.file.path)
|
289
|
+
|
257
290
|
def update_directory(self):
|
258
291
|
for file in [self.file, self.thumbnail]:
|
259
292
|
if not file or not file.path:
|
slm/parsing/__init__.py
CHANGED
@@ -788,6 +788,16 @@ def to_str(value):
|
|
788
788
|
return value
|
789
789
|
|
790
790
|
|
791
|
+
def concat_str(value):
|
792
|
+
"""
|
793
|
+
For multi-line inputs, concatenate the lines, stripping white space at each line
|
794
|
+
break. This is necessary for things like multi-line urls.
|
795
|
+
"""
|
796
|
+
if value is None:
|
797
|
+
return ""
|
798
|
+
return "".join([ln.strip() for ln in value.splitlines()])
|
799
|
+
|
800
|
+
|
791
801
|
class BaseBinder:
|
792
802
|
parsed: BaseParser = None
|
793
803
|
|
slm/parsing/legacy/binding.py
CHANGED
@@ -21,6 +21,7 @@ from slm.parsing import (
|
|
21
21
|
Finding,
|
22
22
|
_Ignored,
|
23
23
|
_Warning,
|
24
|
+
concat_str,
|
24
25
|
remove_from_start,
|
25
26
|
to_alignment,
|
26
27
|
to_antenna,
|
@@ -116,7 +117,7 @@ def to_temp_stab(value):
|
|
116
117
|
value.lower().replace(" ", "").replace("(", "").replace(")", "")
|
117
118
|
== "degc+/-degc"
|
118
119
|
):
|
119
|
-
return _Ignored,
|
120
|
+
return _Ignored("Looks like a placeholder."), None, None
|
120
121
|
|
121
122
|
if "yes" in value.lower() or "indoors" in value.lower():
|
122
123
|
return _Warning(value=True, msg="Interpreted as 'stabilized'"), None, None
|
@@ -638,7 +639,7 @@ class SiteLogBinder(BaseBinder):
|
|
638
639
|
for log_name, bindings in [
|
639
640
|
("Primary Data Center", ("primary", to_str)),
|
640
641
|
("Secondary Data Center", ("secondary", to_str)),
|
641
|
-
("URL for More Information", ("more_info",
|
642
|
+
("URL for More Information", ("more_info", concat_str)),
|
642
643
|
("Site Map", ("sitemap", to_str)),
|
643
644
|
("Site Diagram", ("site_diagram", to_str)),
|
644
645
|
("Horizon Mask", ("horizon_mask", to_str)),
|
slm/receivers/cache.py
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
from django.core.cache import cache
|
2
|
+
from django.dispatch import receiver
|
3
|
+
|
4
|
+
from slm import signals as slm_signals
|
5
|
+
from slm.defines import SiteFileUploadStatus
|
6
|
+
|
7
|
+
|
8
|
+
@receiver(slm_signals.site_published)
|
9
|
+
@receiver(slm_signals.site_file_published)
|
10
|
+
@receiver(slm_signals.site_file_unpublished)
|
11
|
+
def clear_default_cache(**_):
|
12
|
+
"""
|
13
|
+
When events happen that change the public data of the SLM we clear our default cache.
|
14
|
+
"""
|
15
|
+
cache.clear()
|
16
|
+
|
17
|
+
|
18
|
+
@receiver(slm_signals.site_file_deleted)
|
19
|
+
def clear_cache_if_published_file_deleted(sender, **kwargs):
|
20
|
+
"""
|
21
|
+
If a site file attachment is deleted, only clear the caches if that file was published.
|
22
|
+
"""
|
23
|
+
if file := kwargs.pop("upload", None):
|
24
|
+
if file.status is SiteFileUploadStatus.PUBLISHED:
|
25
|
+
cache.clear()
|
slm/settings/root.py
CHANGED
@@ -19,6 +19,7 @@ from split_settings.tools import include, optional
|
|
19
19
|
from slm.settings import env as settings_environment
|
20
20
|
from slm.settings import (
|
21
21
|
get_setting,
|
22
|
+
resource,
|
22
23
|
set_default,
|
23
24
|
slm_path_mk_dirs_must_exist,
|
24
25
|
slm_path_must_exist,
|
@@ -60,9 +61,23 @@ SLM_IGS_VALIDATION = env(
|
|
60
61
|
)
|
61
62
|
|
62
63
|
SLM_ADMIN_MAP = env("SLM_ADMIN_MAP", bool, default=get_setting("SLM_ADMIN_MAP", True))
|
64
|
+
SLM_FILE_VIEWS = env(
|
65
|
+
"SLM_FILE_VIEWS", bool, default=get_setting("SLM_FILE_VIEWS", True)
|
66
|
+
)
|
63
67
|
SLM_SITE_NAME = env("SLM_SITE_NAME", str, default=get_setting("SLM_SITE_NAME", ""))
|
64
68
|
SLM_ORG_NAME = env("SLM_ORG_NAME", str, default=get_setting("SLM_ORG_NAME", "SLM"))
|
65
69
|
|
70
|
+
SLM_FILE_VIEW_FORMATS = env(
|
71
|
+
"SLM_FILE_VIEW_FORMATS",
|
72
|
+
list,
|
73
|
+
default=get_setting("SLM_FILE_VIEW_FORMATS", ["ASCII_9CHAR"]),
|
74
|
+
)
|
75
|
+
SLM_FILE_VIEW_FORMATS = env(
|
76
|
+
"SLM_FILE_VIEW_FORMATS",
|
77
|
+
list,
|
78
|
+
default=get_setting("SLM_FILE_VIEW_FORMATS", ["ASCII_9CHAR"]),
|
79
|
+
)
|
80
|
+
|
66
81
|
# Quick-start development settings - unsuitable for production
|
67
82
|
# See https://docs.djangoproject.com/en/stable/howto/deployment/checklist/
|
68
83
|
|
@@ -111,6 +126,8 @@ INSTALLED_APPS = set_default(
|
|
111
126
|
|
112
127
|
if SLM_ADMIN_MAP:
|
113
128
|
INSTALLED_APPS.insert(0, "slm.map")
|
129
|
+
if SLM_FILE_VIEWS:
|
130
|
+
INSTALLED_APPS.insert(0, "slm.file_views")
|
114
131
|
|
115
132
|
|
116
133
|
SLM_DATABASE = env(
|
@@ -244,3 +261,8 @@ set_default(
|
|
244
261
|
WSGI_APPLICATION = env(
|
245
262
|
"WSGI_APPLICATION", default=get_setting("WSGI_APPLICATION", "slm.wsgi.application")
|
246
263
|
)
|
264
|
+
|
265
|
+
if SLM_ADMIN_MAP or "slm.map" in INSTALLED_APPS:
|
266
|
+
include(resource("slm.map", "settings.py"))
|
267
|
+
if SLM_FILE_VIEWS or "slm.file_views" in INSTALLED_APPS:
|
268
|
+
include(resource("slm.file_views", "settings.py"))
|
slm/settings/routines.py
CHANGED
@@ -27,6 +27,7 @@ routine(
|
|
27
27
|
)
|
28
28
|
|
29
29
|
command("deploy", "check", "--deploy")
|
30
|
+
command("deploy", "check_upgrade", "is-safe")
|
30
31
|
command("deploy", "shellcompletion", "install", switches=["initial"])
|
31
32
|
command("deploy", "migrate", priority=11)
|
32
33
|
command("deploy", "renderstatic", priority=20)
|
slm/settings/slm.py
CHANGED
@@ -6,8 +6,10 @@ from django.utils.translation import gettext_lazy as _
|
|
6
6
|
|
7
7
|
from slm.defines import (
|
8
8
|
AlertLevel,
|
9
|
+
CoordinateMode,
|
9
10
|
GeodesyMLVersion,
|
10
11
|
SiteFileUploadStatus,
|
12
|
+
SiteLogFormat,
|
11
13
|
SiteLogStatus,
|
12
14
|
)
|
13
15
|
from slm.settings import env as settings_environment
|
@@ -186,15 +188,74 @@ set_default(
|
|
186
188
|
# instance than the instance that generates serialized artifacts
|
187
189
|
SLM_FILE_DOMAIN = None
|
188
190
|
|
191
|
+
SLM_IGS_STATION_NAMING = env(
|
192
|
+
"SLM_IGS_STATION_NAMING", default=get_setting("SLM_IGS_STATION_NAMING", False)
|
193
|
+
)
|
194
|
+
if SLM_IGS_STATION_NAMING:
|
195
|
+
# Use IGS naming rules for station names:
|
196
|
+
SLM_STATION_NAME_REGEX = r"[\w]{4}[\d]{2}[\w]{3}"
|
197
|
+
SLM_STATION_NAME_HELP = _(
|
198
|
+
"This is the 9 Character station name (XXXXMRCCC) used in RINEX 3 "
|
199
|
+
"filenames Format: (XXXX - existing four character IGS station "
|
200
|
+
"name, M - Monument or marker number (0-9), R - Receiver number "
|
201
|
+
"(0-9), CCC - Three digit ISO 3166-1 country code)"
|
202
|
+
)
|
203
|
+
else:
|
204
|
+
set_default("SLM_STATION_NAME_REGEX", None)
|
205
|
+
set_default("SLM_STATION_NAME_HELP", _("The name of the station."))
|
206
|
+
|
207
|
+
|
208
|
+
# these settings control site log format precedence and naming
|
209
|
+
SLM_FORMAT_PRIORITY = {
|
210
|
+
SiteLogFormat(fmt): int(priority)
|
211
|
+
for fmt, priority in env(
|
212
|
+
"SLM_FORMAT_PRIORITY", dict, default=get_setting("SLM_FORMAT_PRIORITY", {})
|
213
|
+
)
|
214
|
+
}
|
189
215
|
|
190
|
-
|
191
|
-
set_default("SLM_STATION_NAME_HELP", _("The name of the station."))
|
216
|
+
priorities = SLM_FORMAT_PRIORITY or {fmt: fmt.value for fmt in SiteLogFormat}
|
192
217
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
218
|
+
SLM_DEFAULT_FORMAT = SiteLogFormat(
|
219
|
+
env(
|
220
|
+
"SLM_DEFAULT_FORMAT",
|
221
|
+
str,
|
222
|
+
max(priorities, key=priorities.get),
|
223
|
+
)
|
224
|
+
)
|
225
|
+
|
226
|
+
SLM_FORMAT_EXTENSIONS = {
|
227
|
+
**{fmt: fmt.ext for fmt in SiteLogFormat},
|
228
|
+
**{
|
229
|
+
SiteLogFormat(fmt): ext
|
230
|
+
for fmt, ext in env(
|
231
|
+
"SLM_FORMAT_EXTENSIONS",
|
232
|
+
dict,
|
233
|
+
default=get_setting(
|
234
|
+
"SLM_FORMAT_EXTENSIONS", {fmt: fmt.ext for fmt in SiteLogFormat}
|
235
|
+
),
|
236
|
+
).items()
|
237
|
+
},
|
238
|
+
}
|
239
|
+
|
240
|
+
# when logs are published, the following formats will be rendered to the site log index
|
241
|
+
SLM_ENABLED_FORMATS = [
|
242
|
+
SiteLogFormat(fmt)
|
243
|
+
for fmt in env(
|
244
|
+
"SLM_ENABLED_FORMATS",
|
245
|
+
list,
|
246
|
+
default=get_setting(
|
247
|
+
"SLM_ENABLED_FORMATS", [SiteLogFormat.ASCII_9CHAR, SiteLogFormat.GEODESY_ML]
|
248
|
+
),
|
249
|
+
)
|
250
|
+
]
|
251
|
+
|
252
|
+
if SLM_DEFAULT_FORMAT not in SLM_ENABLED_FORMATS:
|
253
|
+
SLM_ENABLED_FORMATS.insert(0, SLM_DEFAULT_FORMAT)
|
254
|
+
|
255
|
+
|
256
|
+
SLM_COORDINATE_MODE = CoordinateMode(
|
257
|
+
env(
|
258
|
+
"SLM_COORDINATE_MODE",
|
259
|
+
default=get_setting("SLM_COORDINATE_MODE", CoordinateMode.INDEPENDENT),
|
260
|
+
)
|
261
|
+
)
|
slm/settings/urls.py
CHANGED
@@ -92,7 +92,7 @@ def bring_in_urls(urlpatterns):
|
|
92
92
|
urlpatterns.insert(0, path("", include(url_module_str)))
|
93
93
|
|
94
94
|
except ImportError:
|
95
|
-
if app in {"slm", "slm.map", "network_map", "igs_ext"}:
|
95
|
+
if app in {"slm", "slm.map", "slm.file_views", "network_map", "igs_ext"}:
|
96
96
|
raise
|
97
97
|
pass
|
98
98
|
|
slm/settings/validation.py
CHANGED
@@ -18,6 +18,7 @@ from slm.validators import (
|
|
18
18
|
EnumValidator,
|
19
19
|
FieldRequired,
|
20
20
|
NonEmptyValidator,
|
21
|
+
PositionsMatchValidator,
|
21
22
|
TimeRangeBookendValidator,
|
22
23
|
TimeRangeValidator,
|
23
24
|
VerifiedEquipmentValidator,
|
@@ -48,7 +49,7 @@ set_default(
|
|
48
49
|
# 'sitemultipathsources',
|
49
50
|
# 'sitesignalobstructions',
|
50
51
|
# 'sitelocalepisodiceffects',
|
51
|
-
"siteoperationalcontact",
|
52
|
+
# "siteoperationalcontact",
|
52
53
|
# 'siteresponsibleagency',
|
53
54
|
# 'sitemoreinformation'
|
54
55
|
],
|
@@ -68,8 +69,8 @@ set_default(
|
|
68
69
|
"city": [FieldRequired()],
|
69
70
|
"country": [FieldRequired(), EnumValidator()],
|
70
71
|
"tectonic": [EnumValidator()],
|
71
|
-
"xyz": [FieldRequired()],
|
72
|
-
"llh": [FieldRequired()],
|
72
|
+
"xyz": [FieldRequired(), PositionsMatchValidator()],
|
73
|
+
"llh": [FieldRequired(), PositionsMatchValidator()],
|
73
74
|
},
|
74
75
|
"slm.SiteReceiver": {
|
75
76
|
"receiver_type": [VerifiedEquipmentValidator(), ActiveEquipmentValidator()],
|
@@ -93,7 +94,7 @@ set_default(
|
|
93
94
|
TimeRangeBookendValidator(),
|
94
95
|
],
|
95
96
|
"marker_une": [FieldRequired(allow_legacy_nulls=True)],
|
96
|
-
"alignment": [FieldRequired(allow_legacy_nulls=True)],
|
97
|
+
"alignment": [FieldRequired(allow_legacy_nulls=True, desired=True)],
|
97
98
|
},
|
98
99
|
"slm.SiteSurveyedLocalTies": {
|
99
100
|
"name": [FieldRequired()],
|