igs-slm 0.1.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.0b0.dist-info/LICENSE +21 -0
- igs_slm-0.1.0b0.dist-info/METADATA +151 -0
- igs_slm-0.1.0b0.dist-info/RECORD +447 -0
- igs_slm-0.1.0b0.dist-info/WHEEL +4 -0
- igs_slm-0.1.0b0.dist-info/entry_points.txt +3 -0
- igs_tools/__init__.py +0 -0
- igs_tools/connection.py +88 -0
- igs_tools/defines/__init__.py +8 -0
- igs_tools/defines/constellation.py +21 -0
- igs_tools/defines/data_center.py +75 -0
- igs_tools/defines/rinex.py +49 -0
- igs_tools/directory.py +247 -0
- igs_tools/utils.py +66 -0
- slm/__init__.py +21 -0
- slm/admin.py +674 -0
- slm/api/edit/__init__.py +0 -0
- slm/api/edit/serializers.py +316 -0
- slm/api/edit/views.py +1632 -0
- slm/api/fields.py +89 -0
- slm/api/filter.py +504 -0
- slm/api/pagination.py +55 -0
- slm/api/permissions.py +65 -0
- slm/api/public/__init__.py +0 -0
- slm/api/public/serializers.py +249 -0
- slm/api/public/views.py +606 -0
- slm/api/serializers.py +132 -0
- slm/api/views.py +148 -0
- slm/apps.py +323 -0
- slm/authentication.py +198 -0
- slm/bin/__init__.py +0 -0
- slm/bin/startproject.py +262 -0
- slm/bin/templates/{{ project_dir }}/pyproject.toml +35 -0
- slm/bin/templates/{{ project_dir }}/sites/__init__.py +0 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/__init__.py +0 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/base.py +15 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/develop/__init__.py +56 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/develop/local.py +4 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/develop/wsgi.py +16 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/manage.py +34 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/production/__init__.py +61 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/production/wsgi.py +16 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/urls.py +7 -0
- slm/bin/templates/{{ project_dir }}/sites/{{ site }}/validation.py +11 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/__init__.py +0 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/admin.py +5 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/apps.py +14 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/management/__init__.py +0 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/management/commands/__init__.py +0 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/management/commands/import_archive.py +64 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/migrations/__init__.py +0 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/models.py +6 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/templates/slm/base.html +8 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/urls.py +10 -0
- slm/bin/templates/{{ project_dir }}/{{ extension_app }}/views.py +5 -0
- slm/defines/AlertLevel.py +24 -0
- slm/defines/AntennaCalibration.py +25 -0
- slm/defines/AntennaFeatures.py +27 -0
- slm/defines/AntennaReferencePoint.py +22 -0
- slm/defines/Aspiration.py +13 -0
- slm/defines/CardinalDirection.py +19 -0
- slm/defines/CollocationStatus.py +12 -0
- slm/defines/EquipmentState.py +22 -0
- slm/defines/FlagSeverity.py +14 -0
- slm/defines/FractureSpacing.py +15 -0
- slm/defines/FrequencyStandardType.py +15 -0
- slm/defines/GeodesyMLVersion.py +48 -0
- slm/defines/ISOCountry.py +1194 -0
- slm/defines/Instrumentation.py +19 -0
- slm/defines/LogEntryType.py +30 -0
- slm/defines/SLMFileType.py +18 -0
- slm/defines/SiteFileUploadStatus.py +61 -0
- slm/defines/SiteLogFormat.py +49 -0
- slm/defines/SiteLogStatus.py +78 -0
- slm/defines/TectonicPlates.py +28 -0
- slm/defines/__init__.py +46 -0
- slm/forms.py +1126 -0
- slm/jinja2/slm/sitelog/ascii_9char.log +346 -0
- slm/jinja2/slm/sitelog/legacy.log +346 -0
- slm/jinja2/slm/sitelog/xsd/0.4/collocationInformation.xml +12 -0
- slm/jinja2/slm/sitelog/xsd/0.4/condition.xml +12 -0
- slm/jinja2/slm/sitelog/xsd/0.4/contact.xml +52 -0
- slm/jinja2/slm/sitelog/xsd/0.4/formInformation.xml +5 -0
- slm/jinja2/slm/sitelog/xsd/0.4/frequencyStandard.xml +12 -0
- slm/jinja2/slm/sitelog/xsd/0.4/gnssAntenna.xml +16 -0
- slm/jinja2/slm/sitelog/xsd/0.4/gnssReceiver.xml +11 -0
- slm/jinja2/slm/sitelog/xsd/0.4/humiditySensor.xml +13 -0
- slm/jinja2/slm/sitelog/xsd/0.4/localEpisodicEffect.xml +10 -0
- slm/jinja2/slm/sitelog/xsd/0.4/moreInformation.xml +22 -0
- slm/jinja2/slm/sitelog/xsd/0.4/multipathSource.xml +10 -0
- slm/jinja2/slm/sitelog/xsd/0.4/otherInstrumentation.xml +5 -0
- slm/jinja2/slm/sitelog/xsd/0.4/pressureSensor.xml +12 -0
- slm/jinja2/slm/sitelog/xsd/0.4/radioInterference.xml +11 -0
- slm/jinja2/slm/sitelog/xsd/0.4/sensor.xml +16 -0
- slm/jinja2/slm/sitelog/xsd/0.4/signalObstruction.xml +10 -0
- slm/jinja2/slm/sitelog/xsd/0.4/siteIdentification.xml +22 -0
- slm/jinja2/slm/sitelog/xsd/0.4/siteLocation.xml +21 -0
- slm/jinja2/slm/sitelog/xsd/0.4/surveyedLocalTie.xml +20 -0
- slm/jinja2/slm/sitelog/xsd/0.4/temperatureSensor.xml +13 -0
- slm/jinja2/slm/sitelog/xsd/0.4/waterVaporSensor.xml +11 -0
- slm/jinja2/slm/sitelog/xsd/0.5/document.xml +10 -0
- slm/jinja2/slm/sitelog/xsd/geodesyml_0.4.xml +99 -0
- slm/jinja2/slm/sitelog/xsd/geodesyml_0.5.xml +112 -0
- slm/management/__init__.py +0 -0
- slm/management/commands/__init__.py +53 -0
- slm/management/commands/build_index.py +96 -0
- slm/management/commands/generate_sinex.py +675 -0
- slm/management/commands/head_from_index.py +541 -0
- slm/management/commands/import_archive.py +908 -0
- slm/management/commands/import_equipment.py +351 -0
- slm/management/commands/set_site.py +56 -0
- slm/management/commands/sitelog.py +144 -0
- slm/management/commands/synchronize.py +60 -0
- slm/management/commands/update_data_availability.py +167 -0
- slm/management/commands/validate_db.py +186 -0
- slm/management/commands/validate_gml.py +73 -0
- slm/map/__init__.py +1 -0
- slm/map/admin.py +5 -0
- slm/map/api/__init__.py +0 -0
- slm/map/api/edit/__init__.py +0 -0
- slm/map/api/edit/serializers.py +28 -0
- slm/map/api/edit/views.py +46 -0
- slm/map/api/public/__init__.py +0 -0
- slm/map/api/public/serializers.py +29 -0
- slm/map/api/public/views.py +64 -0
- slm/map/apps.py +7 -0
- slm/map/defines.py +53 -0
- slm/map/migrations/0001_initial.py +115 -0
- slm/map/migrations/__init__.py +0 -0
- slm/map/models.py +63 -0
- slm/map/static/slm/css/map.css +86 -0
- slm/map/static/slm/js/map.js +159 -0
- slm/map/templates/slm/map.html +374 -0
- slm/map/templates/slm/station/base.html +11 -0
- slm/map/templates/slm/station/edit.html +10 -0
- slm/map/templates/slm/top_nav.html +17 -0
- slm/map/templatetags/__init__.py +0 -0
- slm/map/templatetags/slm_map.py +18 -0
- slm/map/urls.py +25 -0
- slm/map/views.py +36 -0
- slm/middleware.py +29 -0
- slm/migrations/0001_alter_siteantenna_marker_enu_alter_sitelocation_llh_and_more.py +47 -0
- slm/migrations/0001_initial.py +4826 -0
- slm/migrations/0002_alter_dataavailability_site.py +22 -0
- slm/migrations/0003_remove_logentry_slm_logentr_site_lo_7a2af7_idx_and_more.py +80 -0
- slm/migrations/0004_alter_logentry_timestamp_and_more.py +25 -0
- slm/migrations/0005_alter_logentry_options_alter_logentry_section_and_more.py +46 -0
- slm/migrations/0006_alter_logentry_options_alter_logentry_index_together.py +24 -0
- slm/migrations/0007_alter_dataavailability_rate.py +23 -0
- slm/migrations/0008_alter_archiveindex_options_and_more.py +64 -0
- slm/migrations/0009_alter_archiveindex_end.py +21 -0
- slm/migrations/0010_alter_dataavailability_rinex_version_and_more.py +844 -0
- slm/migrations/0011_alter_siteidentification_fracture_spacing.py +33 -0
- slm/migrations/0012_alter_logentry_type.py +36 -0
- slm/migrations/0013_unpublishedfilesalert.py +48 -0
- slm/migrations/0014_sitelogpublished.py +48 -0
- slm/migrations/0015_alter_siteantenna_options_and_more.py +181 -0
- slm/migrations/0016_alter_antenna_description_alter_radome_description_and_more.py +42 -0
- slm/migrations/0017_alter_logentry_unique_together_and_more.py +54 -0
- slm/migrations/0018_afix_deleted.py +34 -0
- slm/migrations/0018_alter_siteantenna_options_and_more.py +244 -0
- slm/migrations/0019_remove_siteantenna_marker_enu_siteantenna_marker_une_and_more.py +101 -0
- slm/migrations/0020_alter_manufacturer_options.py +16 -0
- slm/migrations/0021_alter_siteform_report_type.py +23 -0
- slm/migrations/0022_rename_antcal_antenna_radome_slm_antcal_antenna_20827a_idx_and_more.py +297 -0
- slm/migrations/0023_archivedsitelog_gml_version_and_more.py +55 -0
- slm/migrations/0024_alter_agency_name_alter_agency_shortname.py +24 -0
- slm/migrations/0025_alter_archivedsitelog_log_format_and_more.py +61 -0
- slm/migrations/0026_alter_archivedsitelog_log_format_and_more.py +61 -0
- slm/migrations/0027_importalert_file_contents_importalert_findings_and_more.py +41 -0
- slm/migrations/0028_antenna_replaced_manufacturer_url_radome_replaced_and_more.py +46 -0
- slm/migrations/0029_manufacturer_full_name.py +17 -0
- slm/migrations/0030_alter_antenna_state_alter_radome_state_and_more.py +43 -0
- slm/migrations/__init__.py +0 -0
- slm/migrations/load_satellitesystems.py +27 -0
- slm/models/__init__.py +118 -0
- slm/models/about.py +14 -0
- slm/models/alerts.py +1204 -0
- slm/models/data.py +58 -0
- slm/models/equipment.py +229 -0
- slm/models/help.py +14 -0
- slm/models/index.py +428 -0
- slm/models/sitelog.py +4279 -0
- slm/models/system.py +723 -0
- slm/models/user.py +304 -0
- slm/parsing/__init__.py +786 -0
- slm/parsing/legacy/__init__.py +4 -0
- slm/parsing/legacy/binding.py +817 -0
- slm/parsing/legacy/parser.py +377 -0
- slm/parsing/xsd/__init__.py +34 -0
- slm/parsing/xsd/binding.py +86 -0
- slm/parsing/xsd/geodesyml/0.4/commonTypes.xsd +133 -0
- slm/parsing/xsd/geodesyml/0.4/contact.xsd +29 -0
- slm/parsing/xsd/geodesyml/0.4/dataStreams.xsd +129 -0
- slm/parsing/xsd/geodesyml/0.4/document.xsd +64 -0
- slm/parsing/xsd/geodesyml/0.4/equipment.xsd +427 -0
- slm/parsing/xsd/geodesyml/0.4/fieldMeasurement.xsd +170 -0
- slm/parsing/xsd/geodesyml/0.4/geodesyML.xsd +71 -0
- slm/parsing/xsd/geodesyml/0.4/geodeticEquipment.xsd +343 -0
- slm/parsing/xsd/geodesyml/0.4/geodeticMonument.xsd +147 -0
- slm/parsing/xsd/geodesyml/0.4/lineage.xsd +614 -0
- slm/parsing/xsd/geodesyml/0.4/localInterferences.xsd +131 -0
- slm/parsing/xsd/geodesyml/0.4/measurement.xsd +473 -0
- slm/parsing/xsd/geodesyml/0.4/monumentInfo.xsd +251 -0
- slm/parsing/xsd/geodesyml/0.4/observationSystem.xsd +429 -0
- slm/parsing/xsd/geodesyml/0.4/project.xsd +38 -0
- slm/parsing/xsd/geodesyml/0.4/quality.xsd +176 -0
- slm/parsing/xsd/geodesyml/0.4/referenceFrame.xsd +194 -0
- slm/parsing/xsd/geodesyml/0.4/siteLog.xsd +71 -0
- slm/parsing/xsd/geodesyml/0.5/commonTypes.xsd +133 -0
- slm/parsing/xsd/geodesyml/0.5/contact.xsd +29 -0
- slm/parsing/xsd/geodesyml/0.5/dataStreams.xsd +129 -0
- slm/parsing/xsd/geodesyml/0.5/document.xsd +64 -0
- slm/parsing/xsd/geodesyml/0.5/equipment.xsd +427 -0
- slm/parsing/xsd/geodesyml/0.5/fieldMeasurement.xsd +170 -0
- slm/parsing/xsd/geodesyml/0.5/geodesyML.xsd +71 -0
- slm/parsing/xsd/geodesyml/0.5/geodeticEquipment.xsd +343 -0
- slm/parsing/xsd/geodesyml/0.5/geodeticMonument.xsd +147 -0
- slm/parsing/xsd/geodesyml/0.5/lineage.xsd +614 -0
- slm/parsing/xsd/geodesyml/0.5/localInterferences.xsd +131 -0
- slm/parsing/xsd/geodesyml/0.5/measurement.xsd +473 -0
- slm/parsing/xsd/geodesyml/0.5/monumentInfo.xsd +306 -0
- slm/parsing/xsd/geodesyml/0.5/observationSystem.xsd +429 -0
- slm/parsing/xsd/geodesyml/0.5/project.xsd +38 -0
- slm/parsing/xsd/geodesyml/0.5/quality.xsd +176 -0
- slm/parsing/xsd/geodesyml/0.5/referenceFrame.xsd +194 -0
- slm/parsing/xsd/geodesyml/0.5/siteLog.xsd +73 -0
- slm/parsing/xsd/parser.py +116 -0
- slm/parsing/xsd/resolver.py +28 -0
- slm/receivers/__init__.py +11 -0
- slm/receivers/alerts.py +87 -0
- slm/receivers/cleanup.py +41 -0
- slm/receivers/event_loggers.py +175 -0
- slm/receivers/index.py +67 -0
- slm/settings/__init__.py +55 -0
- slm/settings/auth.py +15 -0
- slm/settings/ckeditor.py +14 -0
- slm/settings/debug.py +47 -0
- slm/settings/internationalization.py +12 -0
- slm/settings/logging.py +113 -0
- slm/settings/platform/__init__.py +0 -0
- slm/settings/platform/darwin.py +10 -0
- slm/settings/rest.py +21 -0
- slm/settings/root.py +152 -0
- slm/settings/routines.py +43 -0
- slm/settings/secrets.py +37 -0
- slm/settings/security.py +5 -0
- slm/settings/slm.py +188 -0
- slm/settings/static_templates.py +53 -0
- slm/settings/templates.py +29 -0
- slm/settings/uploads.py +8 -0
- slm/settings/urls.py +126 -0
- slm/settings/validation.py +196 -0
- slm/signals.py +250 -0
- slm/singleton.py +49 -0
- slm/static/rest_framework/css/bootstrap-tweaks.css +204 -0
- slm/static/rest_framework/css/bootstrap.min.css +7 -0
- slm/static/rest_framework/css/bootstrap.min.css.map +1 -0
- slm/static/rest_framework/css/default.css +82 -0
- slm/static/rest_framework/css/prettify.css +30 -0
- slm/static/rest_framework/docs/css/base.css +344 -0
- slm/static/rest_framework/docs/css/highlight.css +125 -0
- slm/static/rest_framework/docs/css/jquery.json-view.min.css +11 -0
- slm/static/rest_framework/docs/img/favicon.ico +0 -0
- slm/static/rest_framework/docs/img/grid.png +0 -0
- slm/static/rest_framework/docs/js/api.js +321 -0
- slm/static/rest_framework/docs/js/highlight.pack.js +2 -0
- slm/static/rest_framework/docs/js/jquery.json-view.min.js +7 -0
- slm/static/rest_framework/img/grid.png +0 -0
- slm/static/rest_framework/js/ajax-form.js +127 -0
- slm/static/rest_framework/js/bootstrap.bundle.min.js +7 -0
- slm/static/rest_framework/js/bootstrap.bundle.min.js.map +1 -0
- slm/static/rest_framework/js/bootstrap.min.js.map +1 -0
- slm/static/rest_framework/js/coreapi-0.1.1.js +2043 -0
- slm/static/rest_framework/js/csrf.js +52 -0
- slm/static/rest_framework/js/default.js +47 -0
- slm/static/rest_framework/js/jquery-3.5.1.min.js +2 -0
- slm/static/rest_framework/js/prettify-min.js +28 -0
- slm/static/slm/css/admin.css +3 -0
- slm/static/slm/css/defines.css +82 -0
- slm/static/slm/css/forms.css +1 -0
- slm/static/slm/css/style.css +1004 -0
- slm/static/slm/img/email-branding.png +0 -0
- slm/static/slm/img/favicon.ico +0 -0
- slm/static/slm/img/login-bg.jpg +0 -0
- slm/static/slm/img/slm-logo.svg +4 -0
- slm/static/slm/js/autocomplete.js +341 -0
- slm/static/slm/js/enums.js +322 -0
- slm/static/slm/js/fileIcons.js +30 -0
- slm/static/slm/js/form.js +404 -0
- slm/static/slm/js/formWidget.js +23 -0
- slm/static/slm/js/persistable.js +33 -0
- slm/static/slm/js/slm.js +1028 -0
- slm/static/slm/js/time24.js +212 -0
- slm/static_templates/slm/css/defines.css +26 -0
- slm/static_templates/slm/js/enums.js +28 -0
- slm/static_templates/slm/js/fileIcons.js +16 -0
- slm/static_templates/slm/js/urls.js +5 -0
- slm/templates/account/base.html +20 -0
- slm/templates/account/email/base.html +43 -0
- slm/templates/account/email/base_message.txt +7 -0
- slm/templates/account/email/email_confirmation_message.html +16 -0
- slm/templates/account/email/email_confirmation_message.txt +7 -0
- slm/templates/account/email/email_confirmation_signup_message.html +1 -0
- slm/templates/account/email/email_confirmation_signup_message.txt +1 -0
- slm/templates/account/email/email_confirmation_signup_subject.txt +1 -0
- slm/templates/account/email/email_confirmation_subject.txt +4 -0
- slm/templates/account/email/password_reset_key_message.html +28 -0
- slm/templates/account/email/password_reset_key_message.txt +9 -0
- slm/templates/account/email/password_reset_key_subject.txt +4 -0
- slm/templates/account/email/unknown_account_message.html +25 -0
- slm/templates/account/email/unknown_account_message.txt +12 -0
- slm/templates/account/email/unknown_account_subject.txt +4 -0
- slm/templates/account/login.html +67 -0
- slm/templates/account/logout.html +38 -0
- slm/templates/account/password_change.html +48 -0
- slm/templates/account/password_reset.html +51 -0
- slm/templates/account/password_reset_done.html +20 -0
- slm/templates/account/password_reset_from_key.html +52 -0
- slm/templates/account/password_reset_from_key_done.html +17 -0
- slm/templates/admin/base.html +7 -0
- slm/templates/messages.html +8 -0
- slm/templates/rest_framework/README +16 -0
- slm/templates/rest_framework/admin/detail.html +10 -0
- slm/templates/rest_framework/admin/dict_value.html +11 -0
- slm/templates/rest_framework/admin/list.html +32 -0
- slm/templates/rest_framework/admin/list_value.html +11 -0
- slm/templates/rest_framework/admin/simple_list_value.html +2 -0
- slm/templates/rest_framework/admin.html +282 -0
- slm/templates/rest_framework/api.html +3 -0
- slm/templates/rest_framework/base.html +334 -0
- slm/templates/rest_framework/docs/auth/basic.html +42 -0
- slm/templates/rest_framework/docs/auth/session.html +40 -0
- slm/templates/rest_framework/docs/auth/token.html +41 -0
- slm/templates/rest_framework/docs/document.html +37 -0
- slm/templates/rest_framework/docs/error.html +71 -0
- slm/templates/rest_framework/docs/index.html +55 -0
- slm/templates/rest_framework/docs/interact.html +57 -0
- slm/templates/rest_framework/docs/langs/javascript-intro.html +5 -0
- slm/templates/rest_framework/docs/langs/javascript.html +15 -0
- slm/templates/rest_framework/docs/langs/python-intro.html +3 -0
- slm/templates/rest_framework/docs/langs/python.html +13 -0
- slm/templates/rest_framework/docs/langs/shell-intro.html +3 -0
- slm/templates/rest_framework/docs/langs/shell.html +6 -0
- slm/templates/rest_framework/docs/link.html +113 -0
- slm/templates/rest_framework/docs/sidebar.html +78 -0
- slm/templates/rest_framework/filters/base.html +16 -0
- slm/templates/rest_framework/filters/ordering.html +17 -0
- slm/templates/rest_framework/filters/search.html +13 -0
- slm/templates/rest_framework/horizontal/checkbox.html +23 -0
- slm/templates/rest_framework/horizontal/checkbox_multiple.html +32 -0
- slm/templates/rest_framework/horizontal/dict_field.html +11 -0
- slm/templates/rest_framework/horizontal/fieldset.html +16 -0
- slm/templates/rest_framework/horizontal/form.html +6 -0
- slm/templates/rest_framework/horizontal/input.html +21 -0
- slm/templates/rest_framework/horizontal/list_field.html +11 -0
- slm/templates/rest_framework/horizontal/list_fieldset.html +13 -0
- slm/templates/rest_framework/horizontal/radio.html +42 -0
- slm/templates/rest_framework/horizontal/select.html +36 -0
- slm/templates/rest_framework/horizontal/select_multiple.html +38 -0
- slm/templates/rest_framework/horizontal/textarea.html +21 -0
- slm/templates/rest_framework/inline/checkbox.html +8 -0
- slm/templates/rest_framework/inline/checkbox_multiple.html +14 -0
- slm/templates/rest_framework/inline/dict_field.html +9 -0
- slm/templates/rest_framework/inline/fieldset.html +6 -0
- slm/templates/rest_framework/inline/form.html +8 -0
- slm/templates/rest_framework/inline/input.html +9 -0
- slm/templates/rest_framework/inline/list_field.html +9 -0
- slm/templates/rest_framework/inline/list_fieldset.html +3 -0
- slm/templates/rest_framework/inline/radio.html +25 -0
- slm/templates/rest_framework/inline/select.html +24 -0
- slm/templates/rest_framework/inline/select_multiple.html +25 -0
- slm/templates/rest_framework/inline/textarea.html +9 -0
- slm/templates/rest_framework/login.html +3 -0
- slm/templates/rest_framework/login_base.html +65 -0
- slm/templates/rest_framework/pagination/numbers.html +47 -0
- slm/templates/rest_framework/pagination/previous_and_next.html +21 -0
- slm/templates/rest_framework/raw_data_form.html +11 -0
- slm/templates/rest_framework/schema.js +3 -0
- slm/templates/rest_framework/vertical/checkbox.html +16 -0
- slm/templates/rest_framework/vertical/checkbox_multiple.html +30 -0
- slm/templates/rest_framework/vertical/dict_field.html +7 -0
- slm/templates/rest_framework/vertical/fieldset.html +13 -0
- slm/templates/rest_framework/vertical/form.html +6 -0
- slm/templates/rest_framework/vertical/input.html +17 -0
- slm/templates/rest_framework/vertical/list_field.html +7 -0
- slm/templates/rest_framework/vertical/list_fieldset.html +7 -0
- slm/templates/rest_framework/vertical/radio.html +40 -0
- slm/templates/rest_framework/vertical/select.html +34 -0
- slm/templates/rest_framework/vertical/select_multiple.html +31 -0
- slm/templates/rest_framework/vertical/textarea.html +17 -0
- slm/templates/slm/about.html +21 -0
- slm/templates/slm/alerts/alert.html +15 -0
- slm/templates/slm/alerts/geodesymlinvalid.html +8 -0
- slm/templates/slm/alerts/importalert.html +10 -0
- slm/templates/slm/alerts.html +18 -0
- slm/templates/slm/auth_menu.html +41 -0
- slm/templates/slm/base.html +195 -0
- slm/templates/slm/emails/alert_issued.html +31 -0
- slm/templates/slm/emails/alert_issued.txt +9 -0
- slm/templates/slm/emails/base.html +6 -0
- slm/templates/slm/emails/changes_rejected.txt +7 -0
- slm/templates/slm/emails/review_requested.txt +7 -0
- slm/templates/slm/forms/widgets/auto_complete.html +21 -0
- slm/templates/slm/forms/widgets/auto_complete_multiple.html +18 -0
- slm/templates/slm/forms/widgets/checkbox_multiple.html +6 -0
- slm/templates/slm/forms/widgets/inline_multi.html +1 -0
- slm/templates/slm/forms/widgets/splitdatetime.html +14 -0
- slm/templates/slm/forms/widgets/time24.html +37 -0
- slm/templates/slm/help.html +54 -0
- slm/templates/slm/messages.html +13 -0
- slm/templates/slm/new_site.html +88 -0
- slm/templates/slm/profile.html +57 -0
- slm/templates/slm/register.html +40 -0
- slm/templates/slm/reports/file_log.html +43 -0
- slm/templates/slm/reports/head_log.html +23 -0
- slm/templates/slm/reports/head_report.html +55 -0
- slm/templates/slm/reports/index_log.html +23 -0
- slm/templates/slm/reports/index_report.html +71 -0
- slm/templates/slm/station/alert.html +8 -0
- slm/templates/slm/station/alerts.html +19 -0
- slm/templates/slm/station/base.html +104 -0
- slm/templates/slm/station/download.html +87 -0
- slm/templates/slm/station/edit.html +283 -0
- slm/templates/slm/station/form.html +110 -0
- slm/templates/slm/station/log.html +18 -0
- slm/templates/slm/station/review.html +461 -0
- slm/templates/slm/station/upload.html +295 -0
- slm/templates/slm/station/uploads/attachment.html +20 -0
- slm/templates/slm/station/uploads/geodesyml.html +1 -0
- slm/templates/slm/station/uploads/image.html +27 -0
- slm/templates/slm/station/uploads/json.html +0 -0
- slm/templates/slm/station/uploads/legacy.html +77 -0
- slm/templates/slm/top_nav.html +14 -0
- slm/templates/slm/user_activity.html +16 -0
- slm/templates/slm/widgets/alert_scroll.html +135 -0
- slm/templates/slm/widgets/filelist.html +258 -0
- slm/templates/slm/widgets/legend.html +12 -0
- slm/templates/slm/widgets/log_scroll.html +88 -0
- slm/templates/slm/widgets/stationlist.html +233 -0
- slm/templatetags/__init__.py +0 -0
- slm/templatetags/jinja2.py +9 -0
- slm/templatetags/slm.py +459 -0
- slm/urls.py +148 -0
- slm/utils.py +299 -0
- slm/validators.py +297 -0
- slm/views.py +654 -0
- slm/widgets.py +134 -0
slm/parsing/__init__.py
ADDED
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from datetime import date, datetime, timezone
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
from dateutil.parser import parse as parse_date
|
|
8
|
+
from dateutil.tz.tz import tzlocal
|
|
9
|
+
from django.contrib.gis.geos import Point
|
|
10
|
+
|
|
11
|
+
from slm.models.equipment import Antenna, Radome, Receiver, SatelliteSystem
|
|
12
|
+
from slm.utils import dddmmssss_to_decimal
|
|
13
|
+
|
|
14
|
+
SPECIAL_CHARACTERS = "().,-_[]{}<>+%"
|
|
15
|
+
NUMERIC_CHARACTERS = ".+-Ee"
|
|
16
|
+
SKIPPED_NUMERICS = "',()"
|
|
17
|
+
NULL_VALUES = [
|
|
18
|
+
"unknown",
|
|
19
|
+
"unkn",
|
|
20
|
+
"n/a",
|
|
21
|
+
"none",
|
|
22
|
+
"not measured",
|
|
23
|
+
"provisional",
|
|
24
|
+
"?",
|
|
25
|
+
"programmable",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
ACCURACY_PREFIXES = [
|
|
29
|
+
"approx.",
|
|
30
|
+
"approx",
|
|
31
|
+
"+-",
|
|
32
|
+
"-+",
|
|
33
|
+
"+/-",
|
|
34
|
+
"-/+",
|
|
35
|
+
"±",
|
|
36
|
+
"~",
|
|
37
|
+
"<",
|
|
38
|
+
"Better than",
|
|
39
|
+
"=/-",
|
|
40
|
+
"+/_", # common QWERTY typos
|
|
41
|
+
]
|
|
42
|
+
METERS = ["m", "m.", "meter", "meters"]
|
|
43
|
+
|
|
44
|
+
TZ_INFOS = {"UT": timezone.utc, "UTUT": timezone.utc}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def remove_from_start(value: str, prefixes: List[str]):
|
|
48
|
+
pattern = re.compile(
|
|
49
|
+
r"^("
|
|
50
|
+
+ "|".join(
|
|
51
|
+
[re.escape(prefix.strip()).replace(r"\ ", r"\s*") for prefix in prefixes]
|
|
52
|
+
)
|
|
53
|
+
+ r")\s*",
|
|
54
|
+
re.IGNORECASE,
|
|
55
|
+
)
|
|
56
|
+
result = pattern.sub("", value).strip()
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class _Ignored:
|
|
62
|
+
msg: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class _Warning:
|
|
67
|
+
"""Used to wrap a warning message with a value as the return type for binding functions"""
|
|
68
|
+
|
|
69
|
+
msg: str
|
|
70
|
+
value: Any
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def normalize(name):
|
|
74
|
+
"""
|
|
75
|
+
Normalization is designed to remove any superficial variable name
|
|
76
|
+
mismatches. We remove all special characters and spaces and then
|
|
77
|
+
upper case the name.
|
|
78
|
+
"""
|
|
79
|
+
for char in SPECIAL_CHARACTERS + " \t":
|
|
80
|
+
name = name.replace(char, "")
|
|
81
|
+
return name.upper().strip()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Finding:
|
|
85
|
+
"""
|
|
86
|
+
A base class for parser/binding findings.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
priority: int = 0
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self, lineno, parser, message, section=None, parameter=None, line=None
|
|
93
|
+
):
|
|
94
|
+
self.lineno = lineno
|
|
95
|
+
self.parser = parser
|
|
96
|
+
self.message = message
|
|
97
|
+
self.section = section
|
|
98
|
+
self.parameter = parameter
|
|
99
|
+
self.line = line
|
|
100
|
+
|
|
101
|
+
def __str__(self):
|
|
102
|
+
return (
|
|
103
|
+
f"({self.lineno+1: 4}) {self.parser.lines[self.lineno]}"
|
|
104
|
+
f'{" " * (80-len(self.parser.lines[self.lineno]))}'
|
|
105
|
+
f"[{self.level.upper()}]: {self.message}"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def level(self) -> str:
|
|
110
|
+
return self.__class__.__name__.lower()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Ignored(Finding):
|
|
114
|
+
level = "I"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Error(Finding):
|
|
118
|
+
priority = 5
|
|
119
|
+
|
|
120
|
+
level = "E"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Warn(Finding):
|
|
124
|
+
priority = 4
|
|
125
|
+
|
|
126
|
+
level = "W"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class BaseParameter:
|
|
130
|
+
name = ""
|
|
131
|
+
parser = None
|
|
132
|
+
section = None
|
|
133
|
+
line_no = None
|
|
134
|
+
line_end = None
|
|
135
|
+
values = None
|
|
136
|
+
|
|
137
|
+
binding = None
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def is_placeholder(self):
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def is_empty(self):
|
|
145
|
+
return not self.value.strip()
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def num_lines(self):
|
|
149
|
+
return self.line_end - self.line_no + 1
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def lines(self):
|
|
153
|
+
return self.parser.lines[self.line_no : self.line_end]
|
|
154
|
+
|
|
155
|
+
def __init__(self, line_no, name, values, parser, section):
|
|
156
|
+
self.line_no = line_no
|
|
157
|
+
self.line_end = line_no
|
|
158
|
+
self.parser = parser
|
|
159
|
+
self.section = section
|
|
160
|
+
self.name = name
|
|
161
|
+
self.values = values
|
|
162
|
+
|
|
163
|
+
def append(self, line_no, match):
|
|
164
|
+
self.line_end = line_no
|
|
165
|
+
self.values.append(match.group(1).strip())
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def value(self):
|
|
169
|
+
return "\n".join(self.values)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def normalized_name(self):
|
|
173
|
+
return normalize(self.name)
|
|
174
|
+
|
|
175
|
+
def bind(self, name, value):
|
|
176
|
+
if self.binding is None:
|
|
177
|
+
self.binding = {}
|
|
178
|
+
self.binding[name] = value
|
|
179
|
+
self.section.bind(name, self, value)
|
|
180
|
+
|
|
181
|
+
def __str__(self):
|
|
182
|
+
if self.is_placeholder:
|
|
183
|
+
return f"{self.name} [PLACEHOLDER]: {self.value}"
|
|
184
|
+
return f"{self.name}: {self.value}"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class BaseSection:
|
|
188
|
+
line_no = None
|
|
189
|
+
line_end = None
|
|
190
|
+
section_number = None
|
|
191
|
+
subsection_number = None
|
|
192
|
+
order = None
|
|
193
|
+
header = ""
|
|
194
|
+
|
|
195
|
+
parameters: Dict[str, BaseParameter]
|
|
196
|
+
example = False
|
|
197
|
+
|
|
198
|
+
_param_binding_: Dict[str, List[BaseParameter]] = None
|
|
199
|
+
_binding_: Dict[str, Union[str, int, float, date, datetime]] = None
|
|
200
|
+
|
|
201
|
+
def get_params(self, name: str) -> List[BaseParameter]:
|
|
202
|
+
"""
|
|
203
|
+
Get parameter by parsing or bound name.
|
|
204
|
+
"""
|
|
205
|
+
self._param_binding_ = self._param_binding_ or {}
|
|
206
|
+
return self._param_binding_.get(
|
|
207
|
+
name,
|
|
208
|
+
(
|
|
209
|
+
[self.parameters[normalize(name)]]
|
|
210
|
+
if normalize(name) in self.parameters
|
|
211
|
+
else []
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def bind(self, name, parameter, value):
|
|
216
|
+
"""
|
|
217
|
+
Bind a parameter to the given name and value.
|
|
218
|
+
"""
|
|
219
|
+
self._binding_ = self._binding_ or {}
|
|
220
|
+
self._param_binding_ = self._param_binding_ or {}
|
|
221
|
+
self._param_binding_.setdefault(name, []).append(parameter)
|
|
222
|
+
self._binding_[name] = value
|
|
223
|
+
|
|
224
|
+
def collate(self, params, param, value):
|
|
225
|
+
"""
|
|
226
|
+
Combine the given bound params into a new single binding.
|
|
227
|
+
"""
|
|
228
|
+
self._binding_ = self._binding_ or {}
|
|
229
|
+
self._param_binding_ = self._param_binding_ or {}
|
|
230
|
+
self._binding_[param] = value
|
|
231
|
+
for collated in params:
|
|
232
|
+
parsed_params = self._param_binding_.get(collated, [])
|
|
233
|
+
self._param_binding_.setdefault(param, []).extend(parsed_params)
|
|
234
|
+
if parsed_params:
|
|
235
|
+
del self._param_binding_[collated]
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def binding(self) -> Optional[Dict[str, Union[str, int, float, date, datetime]]]:
|
|
239
|
+
return self._binding_
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def ordering_id(self):
|
|
243
|
+
if self.order:
|
|
244
|
+
return self.order
|
|
245
|
+
if self.subsection_number:
|
|
246
|
+
return self.subsection_number
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
def __init__(self, line_no, section_number, subsection_number, order, parser):
|
|
250
|
+
self.parameters = {}
|
|
251
|
+
self.line_no = line_no
|
|
252
|
+
self.line_end = line_no
|
|
253
|
+
self.section_number = section_number
|
|
254
|
+
self.subsection_number = subsection_number
|
|
255
|
+
self.order = order
|
|
256
|
+
self.parser = parser
|
|
257
|
+
|
|
258
|
+
if isinstance(self.subsection_number, str) and self.subsection_number.isdigit():
|
|
259
|
+
self.subsection_number = int(self.subsection_number)
|
|
260
|
+
|
|
261
|
+
if isinstance(self.order, str) and self.order.isdigit():
|
|
262
|
+
self.order = int(self.order)
|
|
263
|
+
|
|
264
|
+
def __str__(self):
|
|
265
|
+
section_str = f"{self.index_string} {self.header}\n"
|
|
266
|
+
if self.example:
|
|
267
|
+
section_str = f"{self.index_string} {self.header} (EXAMPLE)\n"
|
|
268
|
+
for name, param in self.parameters.items():
|
|
269
|
+
section_str += f"\t{param}\n"
|
|
270
|
+
return section_str
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def contains_values(self):
|
|
274
|
+
"""
|
|
275
|
+
True if any parameter in this section contains a real value that is
|
|
276
|
+
not a placeholder.
|
|
277
|
+
"""
|
|
278
|
+
for name, param in self.parameters.items():
|
|
279
|
+
if not (param.is_empty or param.is_placeholder):
|
|
280
|
+
return True
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def index_string(self):
|
|
285
|
+
index = f"{self.section_number}"
|
|
286
|
+
if self.subsection_number or self.example:
|
|
287
|
+
index += "."
|
|
288
|
+
if self.subsection_number:
|
|
289
|
+
index += str(self.subsection_number)
|
|
290
|
+
else:
|
|
291
|
+
index += "x"
|
|
292
|
+
if self.subsection_number and (self.order or self.example):
|
|
293
|
+
index += f'.{self.order if self.order else "x"}'
|
|
294
|
+
return index
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def index_tuple(self):
|
|
298
|
+
return self.section_number, self.subsection_number, self.order
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def heading_index(self):
|
|
302
|
+
if self.order is not None:
|
|
303
|
+
return self.section_number, self.subsection_number
|
|
304
|
+
return self.section_number
|
|
305
|
+
|
|
306
|
+
def add_parameter(self, parameter):
|
|
307
|
+
if parameter.normalized_name in self.parameters:
|
|
308
|
+
self.parser.add_finding(
|
|
309
|
+
Error(
|
|
310
|
+
parameter.line_no,
|
|
311
|
+
self.parser,
|
|
312
|
+
f"Duplicate parameter: {parameter.name}",
|
|
313
|
+
section=self,
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
self.parameters[parameter.normalized_name] = parameter
|
|
318
|
+
if self.line_end < parameter.line_end:
|
|
319
|
+
self.line_end = parameter.line_end
|
|
320
|
+
return parameter
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class BaseParser:
|
|
324
|
+
"""
|
|
325
|
+
A base parser that tracks findings at specific lines.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
lines: Optional[List[str]]
|
|
329
|
+
|
|
330
|
+
name_matched: bool = None
|
|
331
|
+
site_name: str = None
|
|
332
|
+
|
|
333
|
+
_findings_: Dict[int, Finding]
|
|
334
|
+
|
|
335
|
+
"""
|
|
336
|
+
Sections indexed by section number tuple
|
|
337
|
+
(section_number, subsection_number, order)
|
|
338
|
+
"""
|
|
339
|
+
sections: Dict[Tuple[int, Optional[int], Optional[int]], BaseSection]
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def findings(self) -> Dict[int, Finding]:
|
|
343
|
+
"""
|
|
344
|
+
Return findings dictionary keyed by line number and ordered by
|
|
345
|
+
line number.
|
|
346
|
+
"""
|
|
347
|
+
# dictionaries are ordered in python 3.7+
|
|
348
|
+
return dict(sorted(self._findings_.items()))
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def findings_context(self) -> Dict[int, Tuple[str, str]]:
|
|
352
|
+
return {
|
|
353
|
+
int(line): (finding.level, finding.message)
|
|
354
|
+
for line, finding in self.findings.items()
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def context(self):
|
|
359
|
+
return {"site": self.site_name, "findings": self.findings_context}
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def ignored(self):
|
|
363
|
+
return {
|
|
364
|
+
lineno: finding
|
|
365
|
+
for lineno, finding in self._findings_.items()
|
|
366
|
+
if isinstance(finding, Ignored)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def errors(self):
|
|
371
|
+
return {
|
|
372
|
+
lineno: finding
|
|
373
|
+
for lineno, finding in self._findings_.items()
|
|
374
|
+
if isinstance(finding, Error)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def warnings(self):
|
|
379
|
+
return {
|
|
380
|
+
lineno: finding
|
|
381
|
+
for lineno, finding in self._findings_.items()
|
|
382
|
+
if isinstance(finding, Warn)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def is_valid(self):
|
|
387
|
+
return len(self.errors) == 0
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def has_warnings(self):
|
|
391
|
+
return len(self.warnings) == 0
|
|
392
|
+
|
|
393
|
+
def remove_finding(self, lineno):
|
|
394
|
+
if lineno in self._findings_:
|
|
395
|
+
del self._findings_[lineno]
|
|
396
|
+
|
|
397
|
+
def add_section(self, section):
|
|
398
|
+
if section.index_tuple in self.sections:
|
|
399
|
+
self.duplicate_section_error(section)
|
|
400
|
+
self.sections[section.index_tuple] = section
|
|
401
|
+
return section
|
|
402
|
+
|
|
403
|
+
def duplicate_section_error(self, duplicate_section):
|
|
404
|
+
for lineno in range(duplicate_section.line_no, duplicate_section.line_end):
|
|
405
|
+
self.add_finding(
|
|
406
|
+
Error(
|
|
407
|
+
lineno,
|
|
408
|
+
self,
|
|
409
|
+
f"Duplicate section {duplicate_section.index_string}",
|
|
410
|
+
section=duplicate_section,
|
|
411
|
+
)
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def add_finding(self, finding):
|
|
415
|
+
if not isinstance(finding, Finding):
|
|
416
|
+
raise ValueError(
|
|
417
|
+
f"add_finding() expected a {Finding.__class__.__name__} "
|
|
418
|
+
f"object, was given: {finding.__class__.__name__}."
|
|
419
|
+
)
|
|
420
|
+
self._findings_[finding.lineno] = finding
|
|
421
|
+
|
|
422
|
+
def __init__(self, site_log: Union[str, List[str]], site_name: str = None) -> None:
|
|
423
|
+
"""
|
|
424
|
+
:param site_log: The entire site log contents as a string or as a list
|
|
425
|
+
of lines
|
|
426
|
+
:param site_name: The expected 9-character site name of this site or
|
|
427
|
+
None (default) if name is unknown
|
|
428
|
+
"""
|
|
429
|
+
self.sections = {}
|
|
430
|
+
self.site_name = site_name.upper() if site_name else site_name
|
|
431
|
+
if isinstance(site_log, str):
|
|
432
|
+
self.lines = site_log.split("\n")
|
|
433
|
+
elif isinstance(site_log, list):
|
|
434
|
+
self.lines = site_log
|
|
435
|
+
else:
|
|
436
|
+
raise ValueError(
|
|
437
|
+
f"Expected site_log input to be string or list of lines, "
|
|
438
|
+
f"given: {type(site_log)}."
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
self._findings_ = {}
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def try_prefixes(value, converter, delimiter=" "):
|
|
445
|
+
"""
|
|
446
|
+
A wrapper for converter functions that tries successively shorter strings
|
|
447
|
+
as determined by the given delimiter. Think of this as being robust to junk
|
|
448
|
+
at the end of a value.
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
def try_parse(prefix):
|
|
452
|
+
return converter(prefix)
|
|
453
|
+
|
|
454
|
+
parts = value.split(delimiter)
|
|
455
|
+
trailing = []
|
|
456
|
+
while parts:
|
|
457
|
+
try:
|
|
458
|
+
success = try_parse(" ".join(parts))
|
|
459
|
+
if trailing:
|
|
460
|
+
return _Warning(
|
|
461
|
+
msg=f"Unexpected trailing characters: {' '.join(reversed(trailing))}",
|
|
462
|
+
value=success.value if isinstance(success, _Warning) else success,
|
|
463
|
+
)
|
|
464
|
+
return success
|
|
465
|
+
except ValueError:
|
|
466
|
+
trailing.append(parts.pop())
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def to_antenna(value):
|
|
470
|
+
antenna = value.strip()
|
|
471
|
+
parts = value.split()
|
|
472
|
+
if len(parts) > 1 and len(antenna) > 16:
|
|
473
|
+
antenna = value.rstrip(parts[-1]).strip()
|
|
474
|
+
try:
|
|
475
|
+
return Antenna.objects.get(model__iexact=antenna).model
|
|
476
|
+
except Antenna.DoesNotExist:
|
|
477
|
+
antennas = "\n".join([ant.model for ant in Antenna.objects.public()])
|
|
478
|
+
raise ValueError(
|
|
479
|
+
f"Unexpected antenna model {antenna}. Must be one of " f"\n{antennas}"
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def to_radome(value):
|
|
484
|
+
radome = value.strip()
|
|
485
|
+
try:
|
|
486
|
+
return Radome.objects.get(model__iexact=radome).model
|
|
487
|
+
except Radome.DoesNotExist:
|
|
488
|
+
radomes = "\n".join([rad.model for rad in Radome.objects.public()])
|
|
489
|
+
raise ValueError(
|
|
490
|
+
f"Unexpected radome model {radome}. Must be one of " f"\n{radomes}"
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def to_receiver(value):
|
|
495
|
+
receiver = value.strip()
|
|
496
|
+
try:
|
|
497
|
+
return Receiver.objects.get(model__iexact=receiver).model
|
|
498
|
+
except Receiver.DoesNotExist:
|
|
499
|
+
receivers = "\n".join([rec.model for rec in Receiver.objects.public()])
|
|
500
|
+
raise ValueError(
|
|
501
|
+
f"Unexpected receiver model {receiver}. Must be one of " f"\n{receivers}"
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def to_satellites(value):
|
|
506
|
+
sats = []
|
|
507
|
+
bad_sats = set()
|
|
508
|
+
for sat in [sat for sat in value.split("+") if sat]:
|
|
509
|
+
try:
|
|
510
|
+
upper_sat = sat.upper()
|
|
511
|
+
if upper_sat == "GLONASS":
|
|
512
|
+
sat = "GLO"
|
|
513
|
+
elif upper_sat == "BEIDOU":
|
|
514
|
+
sat = "BDS"
|
|
515
|
+
elif upper_sat == "GALILEO":
|
|
516
|
+
sat = "GAL"
|
|
517
|
+
sats.append(SatelliteSystem.objects.get(name__iexact=sat).pk)
|
|
518
|
+
except SatelliteSystem.DoesNotExist:
|
|
519
|
+
bad_sats.add(sat)
|
|
520
|
+
if bad_sats:
|
|
521
|
+
bad_sats = " \n".join(bad_sats)
|
|
522
|
+
good_sats = " \n".join([sat.name for sat in SatelliteSystem.objects.all()])
|
|
523
|
+
raise ValueError(
|
|
524
|
+
f"Expected constellation list delineated by '+' (e.g. GPS+GLO). "
|
|
525
|
+
f"Unexpected values encountered: \n{bad_sats}\n\nMust be one of "
|
|
526
|
+
f"\n{good_sats}"
|
|
527
|
+
)
|
|
528
|
+
return sats
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def to_enum(enum_cls, value, strict=False, blank=None, ignored=None):
|
|
532
|
+
if value:
|
|
533
|
+
ignored = ignored or []
|
|
534
|
+
if value.lower() in [ign.lower() for ign in ignored]:
|
|
535
|
+
return _Ignored(msg=f"{value} is a placeholder.")
|
|
536
|
+
|
|
537
|
+
parts = value.split()
|
|
538
|
+
idx = len(parts)
|
|
539
|
+
while idx > 0:
|
|
540
|
+
try:
|
|
541
|
+
return enum_cls(" ".join(parts[0:idx]))
|
|
542
|
+
except ValueError:
|
|
543
|
+
idx -= 1
|
|
544
|
+
|
|
545
|
+
if not strict:
|
|
546
|
+
return value.strip()
|
|
547
|
+
|
|
548
|
+
valid_list = " \n".join(en.label for en in enum_cls)
|
|
549
|
+
raise ValueError(f"Invalid value {value} must be one of:\n" f"{valid_list}")
|
|
550
|
+
return blank
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def to_numeric(
|
|
554
|
+
numeric_type,
|
|
555
|
+
value: str,
|
|
556
|
+
units: Optional[List[str]] = None,
|
|
557
|
+
prefixes: Optional[List[str]] = None,
|
|
558
|
+
take_first: Optional[bool] = None,
|
|
559
|
+
):
|
|
560
|
+
"""
|
|
561
|
+
The strategy for converting string parameter values to numerics is to chop
|
|
562
|
+
the numeric off the front of the string. If there's any more numeric
|
|
563
|
+
characters in the remainder its an error if they are not in units. Prefixes
|
|
564
|
+
are also tolerated at the start. Certain delimiters are tolerated but complained about.
|
|
565
|
+
|
|
566
|
+
:param numeric_type: The python type to return
|
|
567
|
+
:param value: The string to parse
|
|
568
|
+
:param units: Expected unit strings that will not be warned about.
|
|
569
|
+
:param prefixes: Prefixes that if present will be removed and ignored
|
|
570
|
+
:param take_first: If specified and the value is a range (e.g. 2-3) return the first element if True,
|
|
571
|
+
return the second element if False and raise ValueError if None
|
|
572
|
+
:return the parsed number of type numeric_type - it may also return _Ignored or a _Warning
|
|
573
|
+
:raises: ValueError if no numeric could be parsed
|
|
574
|
+
"""
|
|
575
|
+
if not value:
|
|
576
|
+
return None
|
|
577
|
+
|
|
578
|
+
if prefixes:
|
|
579
|
+
value = remove_from_start(value, prefixes).strip()
|
|
580
|
+
|
|
581
|
+
unit_start = None
|
|
582
|
+
if units:
|
|
583
|
+
# add parenthesis around units if they arent on
|
|
584
|
+
units = [
|
|
585
|
+
*units,
|
|
586
|
+
*[
|
|
587
|
+
f"({unit})"
|
|
588
|
+
for unit in units
|
|
589
|
+
if not (unit[0] == "(" and unit[-1] == ")")
|
|
590
|
+
],
|
|
591
|
+
]
|
|
592
|
+
for unit in units:
|
|
593
|
+
if value.endswith(unit):
|
|
594
|
+
unit_start = -len(unit)
|
|
595
|
+
break
|
|
596
|
+
|
|
597
|
+
digits = ""
|
|
598
|
+
end = None
|
|
599
|
+
skipped = set()
|
|
600
|
+
for end, char in enumerate(value[0:unit_start]):
|
|
601
|
+
if char == " " and (not digits or not digits.isdigit()):
|
|
602
|
+
continue
|
|
603
|
+
if char.isdigit() or char in NUMERIC_CHARACTERS:
|
|
604
|
+
digits += char
|
|
605
|
+
elif char in SKIPPED_NUMERICS:
|
|
606
|
+
skipped.add(char)
|
|
607
|
+
continue
|
|
608
|
+
else:
|
|
609
|
+
break
|
|
610
|
+
|
|
611
|
+
if not digits or digits.lower().endswith("e"):
|
|
612
|
+
if value.startswith("("):
|
|
613
|
+
return _Ignored("Looks like a placeholder.")
|
|
614
|
+
for null in NULL_VALUES:
|
|
615
|
+
if null in value.lower():
|
|
616
|
+
return _Ignored("Looks like a null value.")
|
|
617
|
+
if value.lower() in units or all(
|
|
618
|
+
[part in units for part in value.lower().split(" ")]
|
|
619
|
+
):
|
|
620
|
+
# some systems spit out a unit or units for when no value is present
|
|
621
|
+
return _Ignored("Looks like a null value.")
|
|
622
|
+
raise ValueError(f"Could not convert {value} to type {numeric_type.__name__}.")
|
|
623
|
+
|
|
624
|
+
# there should not be any trailing text in the string unless they are expected units
|
|
625
|
+
skipped = f"Unexpected delimiters: ({' '.join(list(skipped))})" if skipped else None
|
|
626
|
+
if len(digits) < len(value[0:unit_start].strip()) and (
|
|
627
|
+
not units or value[end:].strip().lower() not in [unt.lower() for unt in units]
|
|
628
|
+
):
|
|
629
|
+
msg = f"{skipped} and u" if skipped else "U"
|
|
630
|
+
return _Warning(
|
|
631
|
+
msg=f"{msg}nexpected trailing characters: {value[end:]}",
|
|
632
|
+
value=numeric_type(digits),
|
|
633
|
+
)
|
|
634
|
+
if skipped:
|
|
635
|
+
return _Warning(msg=skipped, value=numeric_type(digits))
|
|
636
|
+
|
|
637
|
+
try:
|
|
638
|
+
return numeric_type(digits)
|
|
639
|
+
except ValueError:
|
|
640
|
+
if take_first is None:
|
|
641
|
+
raise
|
|
642
|
+
numeric = numeric_type(digits.split("-")[0 if take_first else -1])
|
|
643
|
+
return _Warning(
|
|
644
|
+
msg=f"Used {'first' if take_first else 'second'} value ({numeric}).",
|
|
645
|
+
value=numeric,
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def to_float(value, units=None, prefixes=None, take_first=None):
|
|
650
|
+
return to_numeric(
|
|
651
|
+
numeric_type=float,
|
|
652
|
+
value=value,
|
|
653
|
+
units=units,
|
|
654
|
+
prefixes=prefixes,
|
|
655
|
+
take_first=take_first,
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def to_decimal_degrees(value):
|
|
660
|
+
"""
|
|
661
|
+
Converts ISO6709 degrees minutes seconds into decimal degrees.
|
|
662
|
+
:param value:
|
|
663
|
+
:return:
|
|
664
|
+
"""
|
|
665
|
+
flt = to_float(value)
|
|
666
|
+
if isinstance(flt, _Warning):
|
|
667
|
+
return _Warning(msg=flt.msg, value=dddmmssss_to_decimal(flt.value))
|
|
668
|
+
if flt is _Ignored or isinstance(flt, _Ignored):
|
|
669
|
+
return flt
|
|
670
|
+
return dddmmssss_to_decimal(to_float(value))
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def to_int(value, units=None, prefixes=None):
|
|
674
|
+
try:
|
|
675
|
+
to_numeric(numeric_type=int, value=value, units=units, prefixes=prefixes)
|
|
676
|
+
except ValueError:
|
|
677
|
+
from_float = to_numeric(
|
|
678
|
+
numeric_type=float, value=value, units=units, prefixes=prefixes
|
|
679
|
+
)
|
|
680
|
+
return _Warning(
|
|
681
|
+
msg="Value should be an integer.",
|
|
682
|
+
value=int(
|
|
683
|
+
from_float.value if isinstance(from_float, _Warning) else from_float
|
|
684
|
+
),
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def to_seconds(value):
|
|
689
|
+
seconds = to_int(
|
|
690
|
+
value, units=["sec", "second", "seconds", "s.", "s"], prefixes=["every"]
|
|
691
|
+
)
|
|
692
|
+
if isinstance(seconds, _Warning):
|
|
693
|
+
low_val = value.lower()
|
|
694
|
+
if "hour" in low_val or "hr" in low_val:
|
|
695
|
+
seconds = seconds.value * 3600
|
|
696
|
+
return _Warning(msg=f"Converted to {seconds} seconds!", value=seconds)
|
|
697
|
+
elif "minute" in low_val or "min" in low_val:
|
|
698
|
+
seconds = seconds.value * 60
|
|
699
|
+
return _Warning(msg=f"Converted to {seconds} seconds!", value=seconds)
|
|
700
|
+
return seconds
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def to_pressure(value):
|
|
704
|
+
hpa = to_float(
|
|
705
|
+
value, units=["%", "hPa", "% hPa"], prefixes=ACCURACY_PREFIXES, take_first=False
|
|
706
|
+
)
|
|
707
|
+
if isinstance(hpa, _Warning):
|
|
708
|
+
low_val = value.lower()
|
|
709
|
+
if "mb" in low_val or "mbar" in low_val:
|
|
710
|
+
hpa = hpa.value * 100
|
|
711
|
+
return _Warning(msg=f"Converted to {hpa:0.01f} hPa", value=hpa)
|
|
712
|
+
if "mm" in low_val:
|
|
713
|
+
hpa = hpa.value * 133.3
|
|
714
|
+
return _Warning(msg=f"Converted to {hpa:0.01f} hPa", value=hpa)
|
|
715
|
+
return hpa
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def _to_date(value):
|
|
719
|
+
if value.strip():
|
|
720
|
+
if "CCYY-MM-DD" in value.upper():
|
|
721
|
+
return _Ignored
|
|
722
|
+
try:
|
|
723
|
+
return parse_date(value, tzinfos=TZ_INFOS).date()
|
|
724
|
+
except Exception as exc:
|
|
725
|
+
raise ValueError(
|
|
726
|
+
f"Unable to parse {value} into a date. Expected " f"format: CCYY-MM-DD"
|
|
727
|
+
) from exc
|
|
728
|
+
return None
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def _to_datetime(value):
|
|
732
|
+
if value.strip():
|
|
733
|
+
if "CCYY-MM-DD" in value.upper():
|
|
734
|
+
return _Ignored
|
|
735
|
+
try:
|
|
736
|
+
# UT and UTUT has been seen as a timezone specifier in the wild
|
|
737
|
+
dt = parse_date(value, tzinfos=TZ_INFOS)
|
|
738
|
+
if not dt.tzinfo or dt.tzinfo == tzlocal():
|
|
739
|
+
return dt.replace(tzinfo=timezone.utc)
|
|
740
|
+
return dt
|
|
741
|
+
except Exception as exc:
|
|
742
|
+
raise ValueError(
|
|
743
|
+
f"Unable to parse {value} into a date and time. Expected "
|
|
744
|
+
f"format: CCYY-MM-DDThh:mmZ"
|
|
745
|
+
) from exc
|
|
746
|
+
return None
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
to_date = partial(try_prefixes, converter=_to_date)
|
|
750
|
+
to_datetime = partial(try_prefixes, converter=_to_datetime)
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def to_point(x, y, z):
|
|
754
|
+
if x is None or y is None or z is None:
|
|
755
|
+
return None
|
|
756
|
+
return Point(x, y, z)
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def to_alignment(value):
|
|
760
|
+
try:
|
|
761
|
+
return to_float(value, units=["deg", "degrees"])
|
|
762
|
+
except ValueError:
|
|
763
|
+
if "true north" in value.lower():
|
|
764
|
+
return _Warning(msg="Interpreted as zero.", value=0)
|
|
765
|
+
raise
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def to_str(value):
|
|
769
|
+
if value is None:
|
|
770
|
+
return ""
|
|
771
|
+
return value
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
class BaseBinder:
|
|
775
|
+
parsed: BaseParser = None
|
|
776
|
+
|
|
777
|
+
@property
|
|
778
|
+
def lines(self) -> List[str]:
|
|
779
|
+
return self.parsed.lines
|
|
780
|
+
|
|
781
|
+
@property
|
|
782
|
+
def findings(self):
|
|
783
|
+
return self.parsed.findings
|
|
784
|
+
|
|
785
|
+
def __init__(self, parsed: BaseParser):
|
|
786
|
+
self.parsed = parsed
|