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/utils.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from datetime import date, datetime, timedelta
|
|
4
|
+
from math import atan2, cos, sin, sqrt
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from dateutil import parser as date_parser
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
from django.contrib.gis.geos import Point
|
|
10
|
+
from django.core import serializers
|
|
11
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
12
|
+
from PIL import ExifTags, Image
|
|
13
|
+
|
|
14
|
+
PROTOCOL = getattr(settings, "SLM_HTTP_PROTOCOL", None)
|
|
15
|
+
GPS_EPOCH = date(year=1980, month=1, day=6)
|
|
16
|
+
|
|
17
|
+
_site_record = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_record_model():
|
|
21
|
+
global _site_record
|
|
22
|
+
if _site_record is not None:
|
|
23
|
+
return _site_record
|
|
24
|
+
|
|
25
|
+
from django.apps import apps
|
|
26
|
+
|
|
27
|
+
slm_site_record = getattr(settings, "SLM_SITE_RECORD", "slm.DefaultSiteRecord")
|
|
28
|
+
try:
|
|
29
|
+
app_label, model_class = slm_site_record.split(".")
|
|
30
|
+
_site_record = apps.get_app_config(app_label).get(model_class.lower(), None)
|
|
31
|
+
if not _site_record:
|
|
32
|
+
raise ImproperlyConfigured(
|
|
33
|
+
f'SLM_SITE_RECORD "{slm_site_record}" is not a registered ' f"model"
|
|
34
|
+
)
|
|
35
|
+
return _site_record
|
|
36
|
+
except ValueError as ve:
|
|
37
|
+
raise ImproperlyConfigured(
|
|
38
|
+
f"SLM_SITE_RECORD value {slm_site_record} is invalid, must be "
|
|
39
|
+
f'"app_label.ModelClass"'
|
|
40
|
+
) from ve
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def dddmmssss_to_decimal(dddmmssss):
|
|
44
|
+
if dddmmssss is not None:
|
|
45
|
+
if isinstance(dddmmssss, str):
|
|
46
|
+
dddmmssss = float(dddmmssss)
|
|
47
|
+
dddmmssss /= 10000
|
|
48
|
+
degrees = int(dddmmssss)
|
|
49
|
+
minutes = (dddmmssss - degrees) * 100
|
|
50
|
+
seconds = float((minutes - int(minutes)) * 100)
|
|
51
|
+
return degrees + int(minutes) / 60 + seconds / 3600
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def decimal_to_dddmmssss(dec):
|
|
56
|
+
if dec is not None:
|
|
57
|
+
if isinstance(dec, str):
|
|
58
|
+
dec = float(dec)
|
|
59
|
+
degrees = int(dec)
|
|
60
|
+
minutes = (dec - degrees) * 60
|
|
61
|
+
seconds = float(minutes - int(minutes)) * 60
|
|
62
|
+
return degrees * 10000 + int(minutes) * 100 + seconds
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def dddmmss_ss_parts(dec):
|
|
67
|
+
"""
|
|
68
|
+
Return (degrees, minutes, seconds) from decimal degrees
|
|
69
|
+
:param dec: Decimal degrees lat or lon
|
|
70
|
+
:return:
|
|
71
|
+
"""
|
|
72
|
+
if dec is not None:
|
|
73
|
+
if isinstance(dec, str):
|
|
74
|
+
dec = float(dec)
|
|
75
|
+
degrees = int(dec)
|
|
76
|
+
minutes = (dec - degrees) * 60
|
|
77
|
+
seconds = float(minutes - int(minutes)) * 60
|
|
78
|
+
return degrees, abs(int(minutes)), abs(seconds)
|
|
79
|
+
return None, None, None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def set_protocol(request):
|
|
83
|
+
global PROTOCOL
|
|
84
|
+
if not PROTOCOL:
|
|
85
|
+
PROTOCOL = "https" if request.is_secure() else "http"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_protocol():
|
|
89
|
+
global PROTOCOL
|
|
90
|
+
if PROTOCOL is not None:
|
|
91
|
+
return PROTOCOL
|
|
92
|
+
return "https" if getattr(settings, "SECURE_SSL_REDIRECT", False) else "http"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def build_absolute_url(path, request=None):
|
|
96
|
+
if path.startswith("mailto:"):
|
|
97
|
+
return path
|
|
98
|
+
if request:
|
|
99
|
+
return request.build_absolute_uri(path)
|
|
100
|
+
return f'{get_url()}/{path.lstrip("/")}'
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_url():
|
|
104
|
+
from django.contrib.sites.models import Site
|
|
105
|
+
|
|
106
|
+
return f"{get_protocol()}://{Site.objects.get_current().domain}"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def from_email():
|
|
110
|
+
from django.contrib.sites.models import Site
|
|
111
|
+
|
|
112
|
+
return getattr(
|
|
113
|
+
settings, "SERVER_EMAIL", f"noreply@{Site.objects.get_current().domain}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def clear_caches():
|
|
118
|
+
from slm.models import Site, User
|
|
119
|
+
|
|
120
|
+
User.is_moderator.cache_clear()
|
|
121
|
+
Site.is_moderator.cache_clear()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def to_bool(bool_str):
|
|
125
|
+
if bool_str is None:
|
|
126
|
+
return None
|
|
127
|
+
if isinstance(bool_str, str):
|
|
128
|
+
return bool_str.lower() not in ["0", "no", "false"]
|
|
129
|
+
return bool(bool_str)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def to_snake_case(string):
|
|
133
|
+
snake = string
|
|
134
|
+
if string:
|
|
135
|
+
snake = string[0].lower()
|
|
136
|
+
new = False
|
|
137
|
+
for char in string[1:]:
|
|
138
|
+
if char == " ":
|
|
139
|
+
new = True
|
|
140
|
+
elif char.isupper() or new:
|
|
141
|
+
snake += f"_{char.lower()}"
|
|
142
|
+
new = False
|
|
143
|
+
elif char.isalnum():
|
|
144
|
+
snake += char
|
|
145
|
+
return snake
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def date_to_str(date_obj):
|
|
149
|
+
if date_obj:
|
|
150
|
+
return f"{date_obj.year}-{date_obj.month:02}-{date_obj.day:02}"
|
|
151
|
+
return ""
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def gps_week(date_obj=datetime.now()):
|
|
155
|
+
"""
|
|
156
|
+
Return GPS week number for a given datetime, date or date string
|
|
157
|
+
:param date_obj: Date object, datetime object or date string
|
|
158
|
+
:return: 2-tuple: GPS week number, GPS day of week
|
|
159
|
+
:raises ValueError: If date_obj is earlier than the GPS epoch
|
|
160
|
+
"""
|
|
161
|
+
# todo move this to igs_tools
|
|
162
|
+
if date_obj is None:
|
|
163
|
+
date_obj = datetime.now().date()
|
|
164
|
+
if isinstance(date_obj, str):
|
|
165
|
+
date_obj = date_parser.parse(date_obj)
|
|
166
|
+
if isinstance(date_obj, datetime):
|
|
167
|
+
date_obj = date_obj.date()
|
|
168
|
+
delta = date_obj - GPS_EPOCH
|
|
169
|
+
if delta.days >= 0:
|
|
170
|
+
return delta.days // 7, delta.days % 7
|
|
171
|
+
raise ValueError(f"{date_obj} is earlier than the GPS epoch {GPS_EPOCH}.")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def date_from_gps_week(gps_week, day_of_week=0):
|
|
175
|
+
"""
|
|
176
|
+
Return a date object for a given GPS week number and day of week
|
|
177
|
+
:param gps_week: GPS week number
|
|
178
|
+
:param day_of_week: GPS day of week, 0-6
|
|
179
|
+
:return: Date object
|
|
180
|
+
"""
|
|
181
|
+
# todo move this to igs_tools
|
|
182
|
+
return GPS_EPOCH + timedelta(days=gps_week * 7 + day_of_week)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def day_of_year(date_obj=datetime.now()):
|
|
186
|
+
"""
|
|
187
|
+
Return the day of the year for the given object representing a date.
|
|
188
|
+
|
|
189
|
+
:param date_obj: Date object, datetime object or date string
|
|
190
|
+
:return: integer day of year
|
|
191
|
+
"""
|
|
192
|
+
# todo move this to igs_tools
|
|
193
|
+
if isinstance(date_obj, str):
|
|
194
|
+
date_obj = date_parser.parse(date_obj)
|
|
195
|
+
if isinstance(date_obj, datetime):
|
|
196
|
+
date_obj = date_obj.date()
|
|
197
|
+
return (date_obj - date(date_obj.year, 1, 1) + timedelta(days=1)).days
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def http_accepts(accepted_types, mimetype):
|
|
201
|
+
if "*/*" in accepted_types:
|
|
202
|
+
return True
|
|
203
|
+
if mimetype in accepted_types:
|
|
204
|
+
return True
|
|
205
|
+
typ, sub_type = mimetype.split("/")
|
|
206
|
+
if f"{typ}/*" in accepted_types:
|
|
207
|
+
return True
|
|
208
|
+
if f"*/{sub_type}" in accepted_types:
|
|
209
|
+
return True
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class SectionEncoder(json.JSONEncoder):
|
|
214
|
+
def default(self, obj):
|
|
215
|
+
from django.db.models import Manager, Model, QuerySet
|
|
216
|
+
|
|
217
|
+
from slm.models import Equipment, Manufacturer, SiteSection
|
|
218
|
+
|
|
219
|
+
if hasattr(obj, "isoformat"):
|
|
220
|
+
return obj.isoformat()
|
|
221
|
+
if isinstance(obj, SiteSection):
|
|
222
|
+
return {field: getattr(obj, field) for field in obj.site_log_fields()}
|
|
223
|
+
if isinstance(obj, Equipment):
|
|
224
|
+
return {field: getattr(obj, field) for field in ["model", "manufacturer"]}
|
|
225
|
+
if isinstance(obj, Manufacturer):
|
|
226
|
+
return {field: getattr(obj, field) for field in ["name"]}
|
|
227
|
+
|
|
228
|
+
if isinstance(obj, Model):
|
|
229
|
+
# catch-all
|
|
230
|
+
return json.loads(serializers.serialize("json", [obj]))[0]
|
|
231
|
+
|
|
232
|
+
if isinstance(obj, (Manager, QuerySet)):
|
|
233
|
+
return [related for related in obj.all()]
|
|
234
|
+
|
|
235
|
+
if isinstance(obj, Point):
|
|
236
|
+
return obj.coords
|
|
237
|
+
return json.JSONEncoder.default(self, obj)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def get_exif_tags(file_path):
|
|
241
|
+
# not all images have exif, (e.g. gifs)
|
|
242
|
+
image_exif = getattr(Image.open(file_path), "_getexif", lambda: None)()
|
|
243
|
+
if image_exif:
|
|
244
|
+
exif = {
|
|
245
|
+
ExifTags.TAGS[k]: v
|
|
246
|
+
for k, v in image_exif.items()
|
|
247
|
+
if k in ExifTags.TAGS and not isinstance(v, bytes)
|
|
248
|
+
}
|
|
249
|
+
return exif
|
|
250
|
+
return {}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def xyz2llh(xyz):
|
|
254
|
+
a_e = 6378.1366e3 # meters
|
|
255
|
+
f_e = 1 / 298.25642 # IERS2000 standards
|
|
256
|
+
radians2degree = 45 / atan2(1, 1)
|
|
257
|
+
|
|
258
|
+
xyz_array = np.array(xyz) / a_e
|
|
259
|
+
(x, y, z) = (xyz_array[0], xyz_array[1], xyz_array[2])
|
|
260
|
+
e2 = f_e * (2 - f_e)
|
|
261
|
+
z2 = z**2
|
|
262
|
+
p2 = x**2 + y**2
|
|
263
|
+
p = sqrt(p2)
|
|
264
|
+
r = sqrt(p2 + z2)
|
|
265
|
+
mu = atan2(z * (1 - f_e + e2 / r), p)
|
|
266
|
+
phi = atan2(
|
|
267
|
+
z * (1 - f_e) + e2 * (sin(mu)) ** 3, (1 - f_e) * (p - e2 * (cos(mu)) ** 3)
|
|
268
|
+
)
|
|
269
|
+
lat = phi * radians2degree
|
|
270
|
+
lon = atan2(y, x) * radians2degree
|
|
271
|
+
if lon < 0:
|
|
272
|
+
lon = lon + 360
|
|
273
|
+
h = a_e * (p * cos(phi) + z * sin(phi) - sqrt(1 - e2 * (sin(phi)) ** 2))
|
|
274
|
+
|
|
275
|
+
return lat, lon, h
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def convert_9to4(text: str, name: str) -> str:
|
|
279
|
+
"""
|
|
280
|
+
In any text convert the 9 character string to the equivalent 4 character site name.
|
|
281
|
+
"""
|
|
282
|
+
return re.compile(re.escape(name), re.IGNORECASE).sub(
|
|
283
|
+
lambda match: match.group(0)[0:4], text
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def convert_4to9(text: str, name: str) -> str:
|
|
288
|
+
"""
|
|
289
|
+
In any text convert the 4 character string to the equivalent 9 character site name.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
def match_case(match: str) -> str:
|
|
293
|
+
if match.isupper():
|
|
294
|
+
return name.upper()
|
|
295
|
+
elif match.islower():
|
|
296
|
+
return name.lower()
|
|
297
|
+
return name
|
|
298
|
+
|
|
299
|
+
return re.compile(re.escape(name[0:4]), re.IGNORECASE).sub(match_case, text)
|
slm/validators.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
|
|
4
|
+
from django.core.exceptions import ValidationError
|
|
5
|
+
from django.core.validators import RegexValidator
|
|
6
|
+
from django.db.models import Model
|
|
7
|
+
from django.utils.translation import gettext as _
|
|
8
|
+
|
|
9
|
+
from slm.defines import EquipmentState, FlagSeverity
|
|
10
|
+
|
|
11
|
+
# we can't use actual nulls for times because it breaks things like
|
|
12
|
+
# Greatest on MYSQL
|
|
13
|
+
# NULL_TIME = datetime.utcfromtimestamp(0)
|
|
14
|
+
NULL_TIME = datetime.fromtimestamp(0, timezone.utc)
|
|
15
|
+
NULL_VALUES = [None, "", NULL_TIME]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Flag = namedtuple("Flag", "message manual severity")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_validators(model, field):
|
|
22
|
+
from django.conf import settings
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
Get the validator list for a given model and field from validation
|
|
26
|
+
settings.
|
|
27
|
+
|
|
28
|
+
:param model: The Django model name <app_name>.<ModelClass>
|
|
29
|
+
:param field: The field name
|
|
30
|
+
:return:
|
|
31
|
+
"""
|
|
32
|
+
if isinstance(model, Model) or (
|
|
33
|
+
isinstance(model, type) and issubclass(model, Model)
|
|
34
|
+
):
|
|
35
|
+
model = model._meta.label
|
|
36
|
+
return getattr(settings, "SLM_DATA_VALIDATORS", {}).get(model, {}).get(field, [])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# toggle this flag to allow save block bypassing.
|
|
40
|
+
BYPASS_BLOCKS = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def set_bypass(toggle):
|
|
44
|
+
global BYPASS_BLOCKS
|
|
45
|
+
BYPASS_BLOCKS = bool(toggle)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def bypass_block():
|
|
49
|
+
"""
|
|
50
|
+
Return if we should bypass any validation save blocking. This setting
|
|
51
|
+
is controlled by the SLM_VALIDATION_BYPASS_BLOCK setting in settings or
|
|
52
|
+
can be preempted by toggling the BYPASS_BLOCKS flag above.
|
|
53
|
+
|
|
54
|
+
This is important when working with legacy data. All validators should
|
|
55
|
+
respect this flag.
|
|
56
|
+
|
|
57
|
+
:return: True if we should bypass any save blocks
|
|
58
|
+
"""
|
|
59
|
+
from django.conf import settings
|
|
60
|
+
|
|
61
|
+
if BYPASS_BLOCKS is None:
|
|
62
|
+
return getattr(settings, "SLM_VALIDATION_BYPASS_BLOCK")
|
|
63
|
+
return bool(BYPASS_BLOCKS)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class SLMValidator:
|
|
67
|
+
severity = FlagSeverity.NOTIFY
|
|
68
|
+
|
|
69
|
+
def __init__(self, *args, **kwargs):
|
|
70
|
+
self.severity = kwargs.pop("severity", self.severity)
|
|
71
|
+
super().__init__(*args, **kwargs)
|
|
72
|
+
|
|
73
|
+
def __call__(self, instance, field, value):
|
|
74
|
+
raise NotImplementedError()
|
|
75
|
+
|
|
76
|
+
def validate(self, instance, field, validator):
|
|
77
|
+
try:
|
|
78
|
+
validator()
|
|
79
|
+
except ValidationError as ve:
|
|
80
|
+
if self.severity == FlagSeverity.BLOCK_SAVE and not bypass_block():
|
|
81
|
+
raise ve
|
|
82
|
+
self.throw_flag(ve.message, instance, field)
|
|
83
|
+
|
|
84
|
+
def throw_error(self, message, instance, field):
|
|
85
|
+
if self.severity == FlagSeverity.BLOCK_SAVE and not bypass_block():
|
|
86
|
+
raise ValidationError(_(message))
|
|
87
|
+
self.throw_flag(message, instance, field)
|
|
88
|
+
|
|
89
|
+
def throw_flag(self, message, instance, field):
|
|
90
|
+
if not instance._flags:
|
|
91
|
+
instance._flags = {}
|
|
92
|
+
instance._flags[field.name] = message
|
|
93
|
+
instance.save()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class FieldRequired(SLMValidator):
|
|
97
|
+
required_msg = _("This field is required.")
|
|
98
|
+
desired_msg = _("This field is desired.")
|
|
99
|
+
|
|
100
|
+
allow_legacy_nulls = False
|
|
101
|
+
|
|
102
|
+
desired = False
|
|
103
|
+
|
|
104
|
+
def __init__(self, allow_legacy_nulls=allow_legacy_nulls, desired=desired):
|
|
105
|
+
self.allow_legacy_nulls = allow_legacy_nulls
|
|
106
|
+
self.desired = desired
|
|
107
|
+
super().__init__(severity=FlagSeverity.BLOCK_SAVE)
|
|
108
|
+
|
|
109
|
+
def __call__(self, instance, field, value):
|
|
110
|
+
if isinstance(value, str):
|
|
111
|
+
value = value.strip()
|
|
112
|
+
if value in NULL_VALUES:
|
|
113
|
+
if (
|
|
114
|
+
self.desired
|
|
115
|
+
or not self.allow_legacy_nulls
|
|
116
|
+
or instance.get_initial_value(field.name) in NULL_VALUES
|
|
117
|
+
):
|
|
118
|
+
self.throw_flag(self.desired_msg, instance, field)
|
|
119
|
+
else:
|
|
120
|
+
self.throw_error(self.required_msg, instance, field)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class EnumValidator(SLMValidator):
|
|
124
|
+
statement = _("Value not in Enumeration.")
|
|
125
|
+
|
|
126
|
+
def __call__(self, instance, field, value):
|
|
127
|
+
if isinstance(value, str):
|
|
128
|
+
value = value.strip()
|
|
129
|
+
if value not in NULL_VALUES:
|
|
130
|
+
try:
|
|
131
|
+
field.enum(value)
|
|
132
|
+
except ValueError:
|
|
133
|
+
self.throw_error(self.statement, instance, field)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class VerifiedEquipmentValidator(SLMValidator):
|
|
137
|
+
statement = _("This equipment code has not been verified.")
|
|
138
|
+
|
|
139
|
+
def __call__(self, instance, field, value):
|
|
140
|
+
if value.state == EquipmentState.UNVERIFIED:
|
|
141
|
+
self.throw_error(self.statement, instance, field)
|
|
142
|
+
elif value.state == EquipmentState.LEGACY:
|
|
143
|
+
self.throw_error(self.statement, instance, field)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ActiveEquipmentValidator(SLMValidator):
|
|
147
|
+
statement = _("This equipment code is no longer in use.")
|
|
148
|
+
replaced_statement = _("This equipment code has been replaced by: {}.")
|
|
149
|
+
|
|
150
|
+
def __call__(self, instance, field, value):
|
|
151
|
+
if value.state == EquipmentState.LEGACY:
|
|
152
|
+
if value.replaced_by.exists():
|
|
153
|
+
self.throw_error(
|
|
154
|
+
self.replaced_statement.format(
|
|
155
|
+
", ".join([eq.model for eq in value.replaced_by.all()])
|
|
156
|
+
),
|
|
157
|
+
instance,
|
|
158
|
+
field,
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
self.throw_error(self.statement, instance, field)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class NonEmptyValidator(SLMValidator):
|
|
165
|
+
statement = _("More than zero selections should be made.")
|
|
166
|
+
|
|
167
|
+
def __call__(self, instance, field, value):
|
|
168
|
+
if not value.all():
|
|
169
|
+
self.throw_error(self.statement, instance, field)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class FourIDValidator(SLMValidator):
|
|
173
|
+
regex_val = RegexValidator(regex=r"[A-Z0-9]{4}")
|
|
174
|
+
|
|
175
|
+
def __call__(self, instance, field, value):
|
|
176
|
+
self.validate(instance, field, lambda: self.regex_val(value))
|
|
177
|
+
if not instance.site.name.startswith(value):
|
|
178
|
+
self.throw_error(
|
|
179
|
+
f"{field.verbose_name} "
|
|
180
|
+
f'{_("must be the prefix of the 9 character site name")}.',
|
|
181
|
+
instance,
|
|
182
|
+
field,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ARPValidator(SLMValidator):
|
|
187
|
+
def __call__(self, instance, field, value):
|
|
188
|
+
at_arp = getattr(
|
|
189
|
+
getattr(instance, "antenna_type", None), "reference_point", None
|
|
190
|
+
)
|
|
191
|
+
if at_arp != value:
|
|
192
|
+
self.throw_error(
|
|
193
|
+
f'{getattr(value, "name", None)} '
|
|
194
|
+
f'{_("must does not match the antenna reference point: ")}'
|
|
195
|
+
f'{getattr(at_arp, "name", None)}.',
|
|
196
|
+
instance,
|
|
197
|
+
field,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TimeRangeBookendValidator(SLMValidator):
|
|
202
|
+
"""
|
|
203
|
+
Ensures that sections that should not have overlapping time ranges
|
|
204
|
+
are properly bookended - i.e. that the time range fields are closed before
|
|
205
|
+
the next section starts.
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
accessor: str
|
|
209
|
+
bookend_field: str
|
|
210
|
+
|
|
211
|
+
def __init__(
|
|
212
|
+
self, *args, bookend_field="installed", severity=FlagSeverity.NOTIFY, **kwargs
|
|
213
|
+
):
|
|
214
|
+
self.bookend_field = bookend_field
|
|
215
|
+
assert self.bookend_field, "Bookend field must be specified."
|
|
216
|
+
super().__init__(*args, severity=severity, **kwargs)
|
|
217
|
+
|
|
218
|
+
def __call__(self, instance, field, value):
|
|
219
|
+
sections = (
|
|
220
|
+
getattr(
|
|
221
|
+
instance.site,
|
|
222
|
+
instance._meta.get_field("site").remote_field.get_accessor_name(),
|
|
223
|
+
)
|
|
224
|
+
.head()
|
|
225
|
+
.sort(reverse=True)
|
|
226
|
+
)
|
|
227
|
+
last = sections[0]
|
|
228
|
+
for section in sections[1:]:
|
|
229
|
+
# todo - this should be unnecessary when validation system is made
|
|
230
|
+
# more robust
|
|
231
|
+
if "Must end before" in section._flags.get(field.name, ""):
|
|
232
|
+
del section._flags[field.name]
|
|
233
|
+
section.save()
|
|
234
|
+
######
|
|
235
|
+
|
|
236
|
+
last_start = getattr(last, self.bookend_field, None)
|
|
237
|
+
if getattr(section, field.name, None) is None or (
|
|
238
|
+
last_start and last_start < getattr(section, field.name)
|
|
239
|
+
):
|
|
240
|
+
self.throw_error(
|
|
241
|
+
_("Must end before {} starts {}.").format(
|
|
242
|
+
last, getattr(last, self.bookend_field, None)
|
|
243
|
+
),
|
|
244
|
+
section,
|
|
245
|
+
field,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
last = section
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class TimeRangeValidator(SLMValidator):
|
|
252
|
+
start_field = None
|
|
253
|
+
end_field = None
|
|
254
|
+
|
|
255
|
+
def __init__(self, *args, severity=FlagSeverity.BLOCK_SAVE, **kwargs):
|
|
256
|
+
self.start_field = kwargs.pop("start_field", None)
|
|
257
|
+
self.end_field = kwargs.pop("end_field", None)
|
|
258
|
+
assert not (self.start_field and self.end_field)
|
|
259
|
+
super().__init__(*args, severity=severity, **kwargs)
|
|
260
|
+
|
|
261
|
+
def __call__(self, instance, field, value):
|
|
262
|
+
if self.start_field:
|
|
263
|
+
start = getattr(instance, self.start_field, None)
|
|
264
|
+
if start is not None and start != NULL_TIME and value:
|
|
265
|
+
if start > value:
|
|
266
|
+
self.throw_error(
|
|
267
|
+
f"{field.verbose_name} "
|
|
268
|
+
f'{_("must be greater than")} '
|
|
269
|
+
f"{instance._meta.get_field(self.start_field).verbose_name}",
|
|
270
|
+
instance,
|
|
271
|
+
field,
|
|
272
|
+
)
|
|
273
|
+
if self.end_field:
|
|
274
|
+
end = getattr(instance, self.end_field, None)
|
|
275
|
+
if end is not None and end != NULL_TIME:
|
|
276
|
+
if (
|
|
277
|
+
value is None
|
|
278
|
+
or value == NULL_TIME
|
|
279
|
+
and end is not None
|
|
280
|
+
and end != NULL_TIME
|
|
281
|
+
):
|
|
282
|
+
self.throw_error(
|
|
283
|
+
f'{_("Cannot define")} '
|
|
284
|
+
f"{instance._meta.get_field(self.end_field).verbose_name} "
|
|
285
|
+
f'{_("without defining")} '
|
|
286
|
+
f"{field.verbose_name}.",
|
|
287
|
+
instance,
|
|
288
|
+
field,
|
|
289
|
+
)
|
|
290
|
+
elif end < value:
|
|
291
|
+
self.throw_error(
|
|
292
|
+
f"{field.verbose_name} "
|
|
293
|
+
f'{_("must be less than")} '
|
|
294
|
+
f"{instance._meta.get_field(self.end_field).verbose_name}",
|
|
295
|
+
instance,
|
|
296
|
+
field,
|
|
297
|
+
)
|