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/models/system.py
ADDED
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
|
|
6
|
+
from dateutil import parser
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.contrib.contenttypes.models import ContentType
|
|
9
|
+
from django.contrib.gis.db import models as gis_models
|
|
10
|
+
from django.core.files.base import ContentFile
|
|
11
|
+
from django.db import models
|
|
12
|
+
from django.db.models import Q
|
|
13
|
+
from django.urls import reverse
|
|
14
|
+
from django.utils.timezone import is_naive, make_aware, now
|
|
15
|
+
from django.utils.translation import gettext as _
|
|
16
|
+
from django_enum import EnumField
|
|
17
|
+
from PIL import Image
|
|
18
|
+
from polymorphic.managers import PolymorphicManager, PolymorphicQuerySet
|
|
19
|
+
from polymorphic.models import PolymorphicModel
|
|
20
|
+
|
|
21
|
+
from slm.defines import (
|
|
22
|
+
CardinalDirection,
|
|
23
|
+
GeodesyMLVersion,
|
|
24
|
+
LogEntryType,
|
|
25
|
+
SiteFileUploadStatus,
|
|
26
|
+
SiteLogFormat,
|
|
27
|
+
SLMFileType,
|
|
28
|
+
)
|
|
29
|
+
from slm.models.sitelog import DefaultToStrEncoder
|
|
30
|
+
from slm.utils import get_exif_tags
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AgencyManager(models.Manager):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AgencyQuerySet(models.QuerySet):
|
|
38
|
+
def membership(self, user):
|
|
39
|
+
"""Get the agency(s) this user is a member of."""
|
|
40
|
+
if user.is_authenticated:
|
|
41
|
+
if user.is_superuser:
|
|
42
|
+
return self
|
|
43
|
+
return user.agencies.all()
|
|
44
|
+
return self.none()
|
|
45
|
+
|
|
46
|
+
def public(self):
|
|
47
|
+
"""
|
|
48
|
+
Public agencies show up in all unauthenticated interfaces and APIs.
|
|
49
|
+
The must be both active and public.
|
|
50
|
+
:return:
|
|
51
|
+
"""
|
|
52
|
+
return self.filter(active=True, public=True)
|
|
53
|
+
|
|
54
|
+
def visible_to(self, user):
|
|
55
|
+
"""
|
|
56
|
+
If authenticated and superuser return everything. If not authenticated
|
|
57
|
+
return only public stations, if not super user and authenticated return
|
|
58
|
+
all public agencies and any non-public agencies the user is a member
|
|
59
|
+
of.
|
|
60
|
+
|
|
61
|
+
:param user: The user object associated with the request.
|
|
62
|
+
:return:
|
|
63
|
+
"""
|
|
64
|
+
if user.is_authenticated and user.is_superuser:
|
|
65
|
+
return self
|
|
66
|
+
return self.public() | self.membership(user)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Agency(models.Model):
|
|
70
|
+
id = models.AutoField(primary_key=True) # Field name made lowercase.
|
|
71
|
+
name = models.CharField(max_length=100, blank=False, null=False, db_index=True)
|
|
72
|
+
shortname = models.CharField(max_length=20, blank=False, null=False, db_index=True)
|
|
73
|
+
|
|
74
|
+
url = models.URLField(max_length=255, blank=True, null=True)
|
|
75
|
+
|
|
76
|
+
address = models.CharField(max_length=50, blank=True, null=True)
|
|
77
|
+
address2 = models.CharField(max_length=50, blank=True, null=True)
|
|
78
|
+
city = models.CharField(max_length=50, blank=True, null=True)
|
|
79
|
+
state = models.CharField(max_length=30, blank=True, null=True)
|
|
80
|
+
postal_code = models.CharField(max_length=20, blank=True, null=True)
|
|
81
|
+
country = models.CharField(max_length=50, blank=True, null=True)
|
|
82
|
+
phone1 = models.CharField(max_length=20, blank=True, null=True)
|
|
83
|
+
phone2 = models.CharField(max_length=20, blank=True, null=True)
|
|
84
|
+
email1 = models.EmailField(max_length=100, blank=True, null=True)
|
|
85
|
+
email2 = models.EmailField(max_length=100, blank=True, null=True)
|
|
86
|
+
contact = models.CharField(max_length=50, blank=True, null=True)
|
|
87
|
+
other = models.TextField(blank=True, null=True)
|
|
88
|
+
active = models.BooleanField(blank=True, null=False, default=True)
|
|
89
|
+
created = models.DateTimeField(blank=True, auto_now_add=True)
|
|
90
|
+
|
|
91
|
+
public = models.BooleanField(
|
|
92
|
+
blank=True,
|
|
93
|
+
default=True,
|
|
94
|
+
null=False,
|
|
95
|
+
help_text=_(
|
|
96
|
+
"Set to false to exclude all sites affiliated with this agency "
|
|
97
|
+
"from public exposure."
|
|
98
|
+
),
|
|
99
|
+
db_index=True,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
objects = AgencyManager.from_queryset(AgencyQuerySet)()
|
|
103
|
+
|
|
104
|
+
def __str__(self):
|
|
105
|
+
return self.name
|
|
106
|
+
|
|
107
|
+
class Meta:
|
|
108
|
+
managed = True
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class NetworkManager(models.Manager):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class NetworkQuerySet(models.QuerySet):
|
|
116
|
+
def public(self):
|
|
117
|
+
return self.filter(public=True)
|
|
118
|
+
|
|
119
|
+
def visible_to(self, user):
|
|
120
|
+
if user.is_authenticated and user.is_superuser:
|
|
121
|
+
return self
|
|
122
|
+
return self.public()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Network(models.Model):
|
|
126
|
+
name = models.CharField(max_length=100, blank=False, null=False, db_index=True)
|
|
127
|
+
|
|
128
|
+
public = models.BooleanField(
|
|
129
|
+
null=False,
|
|
130
|
+
default=True,
|
|
131
|
+
blank=True,
|
|
132
|
+
db_index=True,
|
|
133
|
+
help_text=_(
|
|
134
|
+
"If false, this network will not appear in publicly facing data,"
|
|
135
|
+
"interfaces and APIs."
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
sites = models.ManyToManyField("slm.Site", related_name="networks")
|
|
140
|
+
|
|
141
|
+
objects = NetworkManager.from_queryset(NetworkQuerySet)()
|
|
142
|
+
|
|
143
|
+
def __str__(self):
|
|
144
|
+
return self.name
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def site_upload_path(instance, filename):
|
|
148
|
+
"""
|
|
149
|
+
file will be saved to:
|
|
150
|
+
MEDIA_ROOT/uploads/<site name>/filename
|
|
151
|
+
|
|
152
|
+
:param instance: The SiteFile instance
|
|
153
|
+
:param filename: The name of the file
|
|
154
|
+
:return: The path where the site file should reside.
|
|
155
|
+
"""
|
|
156
|
+
prefix = ""
|
|
157
|
+
if instance.SUB_DIRECTORY:
|
|
158
|
+
prefix = f"{instance.SUB_DIRECTORY}/"
|
|
159
|
+
return f"{prefix}{instance.site.name}/{filename}"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def site_thumbnail_path(instance, filename):
|
|
163
|
+
"""
|
|
164
|
+
Return the path for the thumbnail image for the given filename.
|
|
165
|
+
|
|
166
|
+
:param instance: The SiteFile instance
|
|
167
|
+
:param filename: The name of the file
|
|
168
|
+
:return: The path where the thumbnail image should reside.
|
|
169
|
+
"""
|
|
170
|
+
parts = str(site_upload_path(instance, filename)).split("/")
|
|
171
|
+
return "/".join([*parts[0:-1], "thumbnails", parts[-1]])
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class SiteFile(models.Model):
|
|
175
|
+
SUB_DIRECTORY = "misc"
|
|
176
|
+
|
|
177
|
+
logger = getLogger("slm.models.system.SiteFile")
|
|
178
|
+
|
|
179
|
+
site = models.ForeignKey(
|
|
180
|
+
"slm.Site",
|
|
181
|
+
on_delete=models.CASCADE,
|
|
182
|
+
null=False,
|
|
183
|
+
help_text=_("The site this file is attached to."),
|
|
184
|
+
related_name="%(class)ss",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
timestamp = models.DateTimeField(
|
|
188
|
+
auto_now_add=True, db_index=True, help_text=_("When the file was uploaded.")
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
file = models.FileField(
|
|
192
|
+
upload_to=site_upload_path,
|
|
193
|
+
null=False,
|
|
194
|
+
max_length=255,
|
|
195
|
+
help_text=_("A pointer to the uploaded file on disk."),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
size = models.PositiveIntegerField(null=True, default=None, blank=True)
|
|
199
|
+
|
|
200
|
+
thumbnail = models.ImageField(
|
|
201
|
+
upload_to=site_thumbnail_path,
|
|
202
|
+
null=True,
|
|
203
|
+
default=None,
|
|
204
|
+
blank=True,
|
|
205
|
+
help_text=_("A pointer to the generated thumbnail file on disk."),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
mimetype = models.CharField(
|
|
209
|
+
max_length=255,
|
|
210
|
+
null=False,
|
|
211
|
+
default="",
|
|
212
|
+
db_index=True,
|
|
213
|
+
help_text=_("The mimetype of the file."),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
file_type = EnumField(
|
|
217
|
+
SLMFileType,
|
|
218
|
+
null=False,
|
|
219
|
+
default=SLMFileType.ATTACHMENT,
|
|
220
|
+
db_index=True,
|
|
221
|
+
help_text=_("The file type of the upload."),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
log_format = EnumField(
|
|
225
|
+
SiteLogFormat,
|
|
226
|
+
null=True,
|
|
227
|
+
default=None,
|
|
228
|
+
db_index=True,
|
|
229
|
+
help_text=_("The site log format. (Only if file_type is Site Log)"),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
gml_version = EnumField(
|
|
233
|
+
GeodesyMLVersion,
|
|
234
|
+
null=True,
|
|
235
|
+
default=None,
|
|
236
|
+
db_index=True,
|
|
237
|
+
help_text=_("The Geodesy ML version. (Only if file_type is GeodesyML)"),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def update_directory(self):
|
|
241
|
+
for file in [self.file, self.thumbnail]:
|
|
242
|
+
if not file or not file.path:
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
old_file_path = file.path
|
|
246
|
+
new_name = file.field.upload_to(self, os.path.basename(file.name))
|
|
247
|
+
new_file_path = file.storage.path(new_name)
|
|
248
|
+
|
|
249
|
+
if old_file_path != new_file_path:
|
|
250
|
+
os.makedirs(os.path.dirname(new_file_path), exist_ok=True)
|
|
251
|
+
file.name = new_name
|
|
252
|
+
os.rename(old_file_path, new_file_path)
|
|
253
|
+
self.save()
|
|
254
|
+
old_dir = os.path.dirname(old_file_path)
|
|
255
|
+
if not os.listdir(old_dir):
|
|
256
|
+
os.rmdir(old_dir)
|
|
257
|
+
|
|
258
|
+
def rotate(self, degrees_ccw=90):
|
|
259
|
+
if self.file and self.file_type is SLMFileType.SITE_IMAGE:
|
|
260
|
+
from PIL import Image
|
|
261
|
+
|
|
262
|
+
img = Image.open(self.file.path)
|
|
263
|
+
out = img.rotate(int(degrees_ccw), expand=True)
|
|
264
|
+
out.save(self.file.path)
|
|
265
|
+
self.generate_thumbnail(regenerate=True)
|
|
266
|
+
|
|
267
|
+
def save(self, *args, **kwargs):
|
|
268
|
+
self._discover_type()
|
|
269
|
+
if self.pk is None:
|
|
270
|
+
self._exif_transpose()
|
|
271
|
+
self.generate_thumbnail()
|
|
272
|
+
if self.file:
|
|
273
|
+
self.size = self.file.size
|
|
274
|
+
else:
|
|
275
|
+
self.size = None
|
|
276
|
+
return super().save(*args, **kwargs)
|
|
277
|
+
|
|
278
|
+
def _discover_type(self):
|
|
279
|
+
if not self.mimetype or self.mimetype == "application/octet-stream":
|
|
280
|
+
import mimetypes
|
|
281
|
+
|
|
282
|
+
self.mimetype = mimetypes.guess_type(self.file.path)[0]
|
|
283
|
+
if not self.mimetype:
|
|
284
|
+
self.mimetype = "application/octet-stream"
|
|
285
|
+
if self.file_type in [SLMFileType.ATTACHMENT, None]:
|
|
286
|
+
self.file_type, self.log_format = self.determine_type(
|
|
287
|
+
self.file, self.mimetype
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
@classmethod
|
|
291
|
+
def determine_type(cls, file, mimetype):
|
|
292
|
+
file_type, log_format = SLMFileType.ATTACHMENT, None
|
|
293
|
+
if mimetype == SiteLogFormat.LEGACY.mimetype:
|
|
294
|
+
# todo - better criteria??
|
|
295
|
+
upl = file.open()
|
|
296
|
+
content = upl.read()
|
|
297
|
+
if (
|
|
298
|
+
b"Site Identification of the GNSS Monument" in content
|
|
299
|
+
and b"Site Location Information" in content
|
|
300
|
+
and b"GNSS Receiver Information" in content
|
|
301
|
+
and b"GNSS Antenna Information" in content
|
|
302
|
+
):
|
|
303
|
+
if b"Nine Character ID" in content:
|
|
304
|
+
return SLMFileType.SITE_LOG, SiteLogFormat.ASCII_9CHAR
|
|
305
|
+
return SLMFileType.SITE_LOG, SiteLogFormat.LEGACY
|
|
306
|
+
elif mimetype == SiteLogFormat.GEODESY_ML.mimetype:
|
|
307
|
+
# todo - determine if this is the right schema etc, otherwise
|
|
308
|
+
# could be an XML file attachment
|
|
309
|
+
return SLMFileType.SITE_LOG, SiteLogFormat.GEODESY_ML
|
|
310
|
+
elif mimetype == SiteLogFormat.JSON.mimetype:
|
|
311
|
+
pass
|
|
312
|
+
elif mimetype and mimetype.split("/")[0] == "image" and "svg" not in mimetype:
|
|
313
|
+
return SLMFileType.SITE_IMAGE, None
|
|
314
|
+
|
|
315
|
+
return file_type, log_format
|
|
316
|
+
|
|
317
|
+
def _exif_transpose(self):
|
|
318
|
+
"""
|
|
319
|
+
Transpose the image to match the EXIF orientation if it exists and
|
|
320
|
+
is other than 1.
|
|
321
|
+
:return:
|
|
322
|
+
"""
|
|
323
|
+
if self.file_type is SLMFileType.SITE_IMAGE and self.file:
|
|
324
|
+
from PIL import ImageOps
|
|
325
|
+
|
|
326
|
+
buffer = BytesIO()
|
|
327
|
+
ImageOps.exif_transpose(Image.open(self.file.open("rb"))).save(
|
|
328
|
+
buffer, format=self.mimetype.split("/")[1].upper()
|
|
329
|
+
)
|
|
330
|
+
buffer.seek(0)
|
|
331
|
+
self.file.save(
|
|
332
|
+
self.file.name,
|
|
333
|
+
ContentFile(buffer.read()),
|
|
334
|
+
save=False,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def generate_thumbnail(self, regenerate=False):
|
|
338
|
+
"""
|
|
339
|
+
Generate a thumbnail image for this file if it is an image.
|
|
340
|
+
|
|
341
|
+
:param regenerate: If true, delete and regenerate the existing image.
|
|
342
|
+
:return:
|
|
343
|
+
"""
|
|
344
|
+
if (
|
|
345
|
+
self.file_type is SLMFileType.SITE_IMAGE
|
|
346
|
+
and (regenerate or not self.has_thumbnail)
|
|
347
|
+
and self.file.path
|
|
348
|
+
):
|
|
349
|
+
try:
|
|
350
|
+
image = Image.open(self.file.open("rb")).copy().convert("RGB")
|
|
351
|
+
max_dim = getattr(settings, "SLM_THUMBNAIL_SIZE", 250)
|
|
352
|
+
image.thumbnail(
|
|
353
|
+
(
|
|
354
|
+
(image.width * (max_dim / image.height), max_dim)
|
|
355
|
+
if image.height > image.width
|
|
356
|
+
else (max_dim, image.height * (max_dim / image.width))
|
|
357
|
+
),
|
|
358
|
+
Image.LANCZOS,
|
|
359
|
+
)
|
|
360
|
+
buffer = BytesIO()
|
|
361
|
+
image.save(buffer, "JPEG")
|
|
362
|
+
buffer.seek(0)
|
|
363
|
+
if self.has_thumbnail:
|
|
364
|
+
try:
|
|
365
|
+
os.remove(self.thumbnail.path)
|
|
366
|
+
except OSError:
|
|
367
|
+
pass
|
|
368
|
+
self.thumbnail.save(
|
|
369
|
+
getattr(
|
|
370
|
+
self,
|
|
371
|
+
"name",
|
|
372
|
+
".".join(
|
|
373
|
+
[*os.path.basename(self.file.path).split(".")[0:-1], "jpeg"]
|
|
374
|
+
),
|
|
375
|
+
),
|
|
376
|
+
ContentFile(buffer.read()),
|
|
377
|
+
save=False,
|
|
378
|
+
)
|
|
379
|
+
buffer.close()
|
|
380
|
+
except Exception:
|
|
381
|
+
self.logger.exception("Error creating thumbnail for %s", self)
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def has_thumbnail(self):
|
|
385
|
+
return (
|
|
386
|
+
self.thumbnail
|
|
387
|
+
and self.thumbnail.path
|
|
388
|
+
and os.path.exists(self.thumbnail.path)
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def __str__(self):
|
|
392
|
+
if hasattr(self, "name"):
|
|
393
|
+
return f"[{self.site.name}] {self.name}"
|
|
394
|
+
return f"[{self.site.name}] {os.path.basename(self.file.path)}"
|
|
395
|
+
|
|
396
|
+
class Meta:
|
|
397
|
+
abstract = True
|
|
398
|
+
ordering = ("-timestamp",)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class SiteFileUploadManager(models.Manager):
|
|
402
|
+
pass
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class SiteFileUploadQuerySet(models.QuerySet):
|
|
406
|
+
@staticmethod
|
|
407
|
+
def public_q(public_sites_only=True):
|
|
408
|
+
"""
|
|
409
|
+
Return a Q object holding the public definition. Which is any file
|
|
410
|
+
that belongs to a public site that is in the PUBLISHED state.
|
|
411
|
+
|
|
412
|
+
:return: Q object holding the definition of "public"
|
|
413
|
+
"""
|
|
414
|
+
from slm.models.sitelog import Site
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
Q(status=SiteFileUploadStatus.PUBLISHED) & Q(site__in=Site.objects.public())
|
|
418
|
+
if public_sites_only
|
|
419
|
+
else Q()
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
def public(self, public_sites_only=True):
|
|
423
|
+
"""
|
|
424
|
+
Fetch the files that are public. This is any file that belongs to a
|
|
425
|
+
public site that is in the PUBLISHED state.
|
|
426
|
+
|
|
427
|
+
:return: A queryset containing the public files
|
|
428
|
+
"""
|
|
429
|
+
return self.filter(self.public_q(public_sites_only))
|
|
430
|
+
|
|
431
|
+
def available_to(self, user):
|
|
432
|
+
"""
|
|
433
|
+
Fetch the files that are available to the given user. This is any
|
|
434
|
+
public file or any file that is attached to a site a user has edit
|
|
435
|
+
permissions to.
|
|
436
|
+
|
|
437
|
+
:param user: The user (may be unauthenticated)
|
|
438
|
+
:return: A queryset containing available files
|
|
439
|
+
"""
|
|
440
|
+
from slm.models.sitelog import Site
|
|
441
|
+
|
|
442
|
+
if not user.is_authenticated:
|
|
443
|
+
return self.public()
|
|
444
|
+
return self.filter(
|
|
445
|
+
self.public_q(public_sites_only=False)
|
|
446
|
+
| (Q(site__in=Site.objects.editable_by(user)))
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
class SiteFileUpload(SiteFile):
|
|
451
|
+
"""
|
|
452
|
+
SiteFileUploads can be any file that was uploaded to a given site.
|
|
453
|
+
This includes, images and attachments as well as legacy site logs,
|
|
454
|
+
GeodesyML files or json files. Access to the files for download publicly
|
|
455
|
+
and by authenticated users is controlled in the manager/queryset above.
|
|
456
|
+
|
|
457
|
+
Only images and attachments are made available for download publicly.
|
|
458
|
+
Uploaded log files are ephemeral - for downloads of rendered log files
|
|
459
|
+
from disk see ArchivedSiteLog.
|
|
460
|
+
"""
|
|
461
|
+
|
|
462
|
+
SUB_DIRECTORY = "uploads"
|
|
463
|
+
|
|
464
|
+
name = models.CharField(
|
|
465
|
+
blank=False, db_index=True, max_length=255, help_text=_("The name of the file.")
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
created = models.DateTimeField(
|
|
469
|
+
null=True,
|
|
470
|
+
blank=True,
|
|
471
|
+
default=None,
|
|
472
|
+
db_index=True,
|
|
473
|
+
help_text=_("The date and time the file was created."),
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
status = EnumField(
|
|
477
|
+
SiteFileUploadStatus,
|
|
478
|
+
default=SiteFileUploadStatus.UNPUBLISHED,
|
|
479
|
+
null=False,
|
|
480
|
+
blank=True,
|
|
481
|
+
db_index=True,
|
|
482
|
+
help_text=_(
|
|
483
|
+
"The status of the file. This will also depend on what type the " "file is."
|
|
484
|
+
),
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
user = models.ForeignKey(
|
|
488
|
+
settings.AUTH_USER_MODEL,
|
|
489
|
+
null=True,
|
|
490
|
+
default=None,
|
|
491
|
+
on_delete=models.SET_NULL,
|
|
492
|
+
help_text=_("The user that uploaded the file."),
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
# context generated at the time of upload - will be added to upload page
|
|
496
|
+
# rendering for the file.
|
|
497
|
+
context = models.JSONField(
|
|
498
|
+
null=False, blank=True, default=dict, encoder=DefaultToStrEncoder
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
description = models.TextField(
|
|
502
|
+
blank=True,
|
|
503
|
+
default="",
|
|
504
|
+
help_text=_("A description of what this file is (optional)."),
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
direction = EnumField(
|
|
508
|
+
CardinalDirection,
|
|
509
|
+
blank=True,
|
|
510
|
+
default=None,
|
|
511
|
+
null=True,
|
|
512
|
+
help_text=_(
|
|
513
|
+
"For images taken at the site, this is the cardinal direction the "
|
|
514
|
+
"camera was pointing towards."
|
|
515
|
+
),
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
objects = SiteFileUploadManager.from_queryset(SiteFileUploadQuerySet)()
|
|
519
|
+
|
|
520
|
+
def save(self, *args, **kwargs):
|
|
521
|
+
self._discover_type()
|
|
522
|
+
# if this is an image, attempt to pull the real timestamp out of the
|
|
523
|
+
# meta data
|
|
524
|
+
if not self.created and self.file_type == SLMFileType.SITE_IMAGE:
|
|
525
|
+
tags = get_exif_tags(self.file.open("rb"))
|
|
526
|
+
for tag in ["DateTime", "DateTimeOriginal", "DateTimeDigitized"]:
|
|
527
|
+
if tag in tags:
|
|
528
|
+
try:
|
|
529
|
+
self.created = datetime.strptime(tags[tag], "%Y:%m:%d %H:%M:%S")
|
|
530
|
+
except ValueError:
|
|
531
|
+
try:
|
|
532
|
+
self.created = parser.parse(tags[tag])
|
|
533
|
+
except parser.ParserError:
|
|
534
|
+
pass
|
|
535
|
+
|
|
536
|
+
if self.created:
|
|
537
|
+
if is_naive(self.created):
|
|
538
|
+
self.created = make_aware(self.created, timezone.utc)
|
|
539
|
+
break
|
|
540
|
+
return super().save(*args, **kwargs)
|
|
541
|
+
|
|
542
|
+
@property
|
|
543
|
+
def link(self):
|
|
544
|
+
return reverse("slm_public_api:files-detail", kwargs={"pk": self.pk})
|
|
545
|
+
|
|
546
|
+
@property
|
|
547
|
+
def edit_link(self):
|
|
548
|
+
return (
|
|
549
|
+
reverse(
|
|
550
|
+
"slm_edit_api:files-detail",
|
|
551
|
+
kwargs={"pk": self.pk, "site": self.site.name},
|
|
552
|
+
)
|
|
553
|
+
+ "?download"
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
@property
|
|
557
|
+
def thumbnail_link(self):
|
|
558
|
+
lnk = reverse("slm_public_api:files-detail", kwargs={"pk": self.pk})
|
|
559
|
+
return f"{lnk}?thumbnail=1"
|
|
560
|
+
|
|
561
|
+
@property
|
|
562
|
+
def edit_thumbnail_link(self):
|
|
563
|
+
lnk = reverse(
|
|
564
|
+
"slm_edit_api:files-detail", kwargs={"pk": self.pk, "site": self.site.name}
|
|
565
|
+
)
|
|
566
|
+
return f"{lnk}?thumbnail=1&download"
|
|
567
|
+
|
|
568
|
+
class Meta:
|
|
569
|
+
ordering = ("-timestamp",)
|
|
570
|
+
verbose_name_plural = "Site File Uploads"
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
class LogEntryManager(PolymorphicManager):
|
|
574
|
+
pass
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
class LogEntryQuerySet(PolymorphicQuerySet):
|
|
578
|
+
def for_user(self, user):
|
|
579
|
+
if user.is_superuser:
|
|
580
|
+
return self
|
|
581
|
+
return self.filter(site__agencies__in=user.agencies.all())
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
class LogEntry(PolymorphicModel):
|
|
585
|
+
user = models.ForeignKey(
|
|
586
|
+
settings.AUTH_USER_MODEL,
|
|
587
|
+
on_delete=models.SET_NULL,
|
|
588
|
+
null=True,
|
|
589
|
+
default=None,
|
|
590
|
+
blank=True,
|
|
591
|
+
related_name="logentries",
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
timestamp = models.DateTimeField(null=False, blank=True, db_index=True)
|
|
595
|
+
|
|
596
|
+
type = EnumField(LogEntryType, null=False, blank=False, db_index=True)
|
|
597
|
+
|
|
598
|
+
site = models.ForeignKey(
|
|
599
|
+
"slm.Site",
|
|
600
|
+
on_delete=models.CASCADE,
|
|
601
|
+
null=True,
|
|
602
|
+
default=None,
|
|
603
|
+
blank=True,
|
|
604
|
+
related_name="edit_history",
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
section = models.ForeignKey(
|
|
608
|
+
ContentType,
|
|
609
|
+
on_delete=models.CASCADE,
|
|
610
|
+
null=True,
|
|
611
|
+
default=None,
|
|
612
|
+
blank=True,
|
|
613
|
+
related_name="edit_history",
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
subsection = models.PositiveSmallIntegerField(null=True, default=None, blank=True)
|
|
617
|
+
|
|
618
|
+
file = models.ForeignKey(
|
|
619
|
+
"slm.SiteFileUpload", null=True, default=None, on_delete=models.SET_NULL
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
ip = models.GenericIPAddressField(null=True, default=None, blank=True)
|
|
623
|
+
|
|
624
|
+
objects = LogEntryManager.from_queryset(LogEntryQuerySet)()
|
|
625
|
+
|
|
626
|
+
def save(self, *args, **kwargs):
|
|
627
|
+
if not self.timestamp:
|
|
628
|
+
self.timestamp = now()
|
|
629
|
+
return super().save(*args, **kwargs)
|
|
630
|
+
|
|
631
|
+
@property
|
|
632
|
+
def link(self):
|
|
633
|
+
"""Return a link to the most relevant view for the log entry."""
|
|
634
|
+
if hasattr(self, "section") and self.section:
|
|
635
|
+
section_link = reverse(
|
|
636
|
+
"slm:edit",
|
|
637
|
+
kwargs={
|
|
638
|
+
"station": self.site.name,
|
|
639
|
+
"section": self.section.model_class().section_slug(),
|
|
640
|
+
},
|
|
641
|
+
)
|
|
642
|
+
if self.subsection is not None and self.type != LogEntryType.DELETE:
|
|
643
|
+
section_link += f"#{self.subsection}"
|
|
644
|
+
return section_link
|
|
645
|
+
elif hasattr(self, "file") and self.file:
|
|
646
|
+
return reverse(
|
|
647
|
+
"slm:upload", kwargs={"station": self.site.name, "file": self.file.id}
|
|
648
|
+
)
|
|
649
|
+
elif self.type in {LogEntryType.ATTACHMENT_DELETE, LogEntryType.IMAGE_DELETE}:
|
|
650
|
+
return reverse("slm:upload", kwargs={"station": self.site.name})
|
|
651
|
+
elif hasattr(self, "site") and self.site:
|
|
652
|
+
return reverse("slm:review", kwargs={"station": self.site.name})
|
|
653
|
+
return None
|
|
654
|
+
|
|
655
|
+
def __str__(self):
|
|
656
|
+
return (
|
|
657
|
+
f"({self.site.name} | "
|
|
658
|
+
f'{self.user.name or self.user.email if self.user else ""}) '
|
|
659
|
+
f"[{self.timestamp}]: {self.type} -> "
|
|
660
|
+
f'{self.section or self.file or self.site or ""}'
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
class Meta:
|
|
664
|
+
ordering = ("-timestamp",)
|
|
665
|
+
verbose_name_plural = "Log Entries"
|
|
666
|
+
verbose_name = "Log Entry"
|
|
667
|
+
unique_together = (("timestamp", "type", "site", "section", "subsection"),)
|
|
668
|
+
indexes = [
|
|
669
|
+
models.Index(fields=("timestamp", "type", "site", "section", "subsection"))
|
|
670
|
+
]
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
class TideGauge(gis_models.Model):
|
|
674
|
+
name = models.CharField(max_length=128, blank=True, null=False, db_index=True)
|
|
675
|
+
|
|
676
|
+
position = gis_models.PointField(null=True, blank=True, srid=4326, geography=True)
|
|
677
|
+
|
|
678
|
+
sonel_id = models.IntegerField(blank=True, null=True, db_index=True)
|
|
679
|
+
|
|
680
|
+
sites = models.ManyToManyField(
|
|
681
|
+
"slm.Site",
|
|
682
|
+
through="slm.SiteTideGauge",
|
|
683
|
+
through_fields=("gauge", "site"),
|
|
684
|
+
related_name="tide_gauges",
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
@property
|
|
688
|
+
def sonel_link(self):
|
|
689
|
+
if self.sonel_id:
|
|
690
|
+
return (
|
|
691
|
+
f"http://www.sonel.org/spip.php?page=maregraphe"
|
|
692
|
+
f"&idStation={self.sonel_id}"
|
|
693
|
+
)
|
|
694
|
+
return ""
|
|
695
|
+
|
|
696
|
+
@property
|
|
697
|
+
def link(self):
|
|
698
|
+
return self.sonel_link # or ...
|
|
699
|
+
|
|
700
|
+
def __str__(self):
|
|
701
|
+
return self.name
|
|
702
|
+
|
|
703
|
+
class Meta:
|
|
704
|
+
verbose_name = "Tide Gauge"
|
|
705
|
+
verbose_name_plural = "Tide Gauges"
|
|
706
|
+
ordering = ("name",)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
class SiteTideGauge(models.Model):
|
|
710
|
+
site = models.ForeignKey(
|
|
711
|
+
"slm.Site", on_delete=models.CASCADE, related_name="tide_gauge_distances"
|
|
712
|
+
)
|
|
713
|
+
gauge = models.ForeignKey(
|
|
714
|
+
TideGauge, on_delete=models.CASCADE, related_name="site_distances"
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
distance = models.IntegerField(blank=True, null=False, db_index=True)
|
|
718
|
+
|
|
719
|
+
def __str__(self):
|
|
720
|
+
return f"{self.site.name} {self.gauge.name}"
|
|
721
|
+
|
|
722
|
+
class Meta:
|
|
723
|
+
ordering = ("site", "distance")
|