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.
Files changed (447) hide show
  1. igs_slm-0.1.0b0.dist-info/LICENSE +21 -0
  2. igs_slm-0.1.0b0.dist-info/METADATA +151 -0
  3. igs_slm-0.1.0b0.dist-info/RECORD +447 -0
  4. igs_slm-0.1.0b0.dist-info/WHEEL +4 -0
  5. igs_slm-0.1.0b0.dist-info/entry_points.txt +3 -0
  6. igs_tools/__init__.py +0 -0
  7. igs_tools/connection.py +88 -0
  8. igs_tools/defines/__init__.py +8 -0
  9. igs_tools/defines/constellation.py +21 -0
  10. igs_tools/defines/data_center.py +75 -0
  11. igs_tools/defines/rinex.py +49 -0
  12. igs_tools/directory.py +247 -0
  13. igs_tools/utils.py +66 -0
  14. slm/__init__.py +21 -0
  15. slm/admin.py +674 -0
  16. slm/api/edit/__init__.py +0 -0
  17. slm/api/edit/serializers.py +316 -0
  18. slm/api/edit/views.py +1632 -0
  19. slm/api/fields.py +89 -0
  20. slm/api/filter.py +504 -0
  21. slm/api/pagination.py +55 -0
  22. slm/api/permissions.py +65 -0
  23. slm/api/public/__init__.py +0 -0
  24. slm/api/public/serializers.py +249 -0
  25. slm/api/public/views.py +606 -0
  26. slm/api/serializers.py +132 -0
  27. slm/api/views.py +148 -0
  28. slm/apps.py +323 -0
  29. slm/authentication.py +198 -0
  30. slm/bin/__init__.py +0 -0
  31. slm/bin/startproject.py +262 -0
  32. slm/bin/templates/{{ project_dir }}/pyproject.toml +35 -0
  33. slm/bin/templates/{{ project_dir }}/sites/__init__.py +0 -0
  34. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/__init__.py +0 -0
  35. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/base.py +15 -0
  36. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/develop/__init__.py +56 -0
  37. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/develop/local.py +4 -0
  38. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/develop/wsgi.py +16 -0
  39. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/manage.py +34 -0
  40. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/production/__init__.py +61 -0
  41. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/production/wsgi.py +16 -0
  42. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/urls.py +7 -0
  43. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/validation.py +11 -0
  44. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/__init__.py +0 -0
  45. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/admin.py +5 -0
  46. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/apps.py +14 -0
  47. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/management/__init__.py +0 -0
  48. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/management/commands/__init__.py +0 -0
  49. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/management/commands/import_archive.py +64 -0
  50. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/migrations/__init__.py +0 -0
  51. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/models.py +6 -0
  52. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/templates/slm/base.html +8 -0
  53. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/urls.py +10 -0
  54. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/views.py +5 -0
  55. slm/defines/AlertLevel.py +24 -0
  56. slm/defines/AntennaCalibration.py +25 -0
  57. slm/defines/AntennaFeatures.py +27 -0
  58. slm/defines/AntennaReferencePoint.py +22 -0
  59. slm/defines/Aspiration.py +13 -0
  60. slm/defines/CardinalDirection.py +19 -0
  61. slm/defines/CollocationStatus.py +12 -0
  62. slm/defines/EquipmentState.py +22 -0
  63. slm/defines/FlagSeverity.py +14 -0
  64. slm/defines/FractureSpacing.py +15 -0
  65. slm/defines/FrequencyStandardType.py +15 -0
  66. slm/defines/GeodesyMLVersion.py +48 -0
  67. slm/defines/ISOCountry.py +1194 -0
  68. slm/defines/Instrumentation.py +19 -0
  69. slm/defines/LogEntryType.py +30 -0
  70. slm/defines/SLMFileType.py +18 -0
  71. slm/defines/SiteFileUploadStatus.py +61 -0
  72. slm/defines/SiteLogFormat.py +49 -0
  73. slm/defines/SiteLogStatus.py +78 -0
  74. slm/defines/TectonicPlates.py +28 -0
  75. slm/defines/__init__.py +46 -0
  76. slm/forms.py +1126 -0
  77. slm/jinja2/slm/sitelog/ascii_9char.log +346 -0
  78. slm/jinja2/slm/sitelog/legacy.log +346 -0
  79. slm/jinja2/slm/sitelog/xsd/0.4/collocationInformation.xml +12 -0
  80. slm/jinja2/slm/sitelog/xsd/0.4/condition.xml +12 -0
  81. slm/jinja2/slm/sitelog/xsd/0.4/contact.xml +52 -0
  82. slm/jinja2/slm/sitelog/xsd/0.4/formInformation.xml +5 -0
  83. slm/jinja2/slm/sitelog/xsd/0.4/frequencyStandard.xml +12 -0
  84. slm/jinja2/slm/sitelog/xsd/0.4/gnssAntenna.xml +16 -0
  85. slm/jinja2/slm/sitelog/xsd/0.4/gnssReceiver.xml +11 -0
  86. slm/jinja2/slm/sitelog/xsd/0.4/humiditySensor.xml +13 -0
  87. slm/jinja2/slm/sitelog/xsd/0.4/localEpisodicEffect.xml +10 -0
  88. slm/jinja2/slm/sitelog/xsd/0.4/moreInformation.xml +22 -0
  89. slm/jinja2/slm/sitelog/xsd/0.4/multipathSource.xml +10 -0
  90. slm/jinja2/slm/sitelog/xsd/0.4/otherInstrumentation.xml +5 -0
  91. slm/jinja2/slm/sitelog/xsd/0.4/pressureSensor.xml +12 -0
  92. slm/jinja2/slm/sitelog/xsd/0.4/radioInterference.xml +11 -0
  93. slm/jinja2/slm/sitelog/xsd/0.4/sensor.xml +16 -0
  94. slm/jinja2/slm/sitelog/xsd/0.4/signalObstruction.xml +10 -0
  95. slm/jinja2/slm/sitelog/xsd/0.4/siteIdentification.xml +22 -0
  96. slm/jinja2/slm/sitelog/xsd/0.4/siteLocation.xml +21 -0
  97. slm/jinja2/slm/sitelog/xsd/0.4/surveyedLocalTie.xml +20 -0
  98. slm/jinja2/slm/sitelog/xsd/0.4/temperatureSensor.xml +13 -0
  99. slm/jinja2/slm/sitelog/xsd/0.4/waterVaporSensor.xml +11 -0
  100. slm/jinja2/slm/sitelog/xsd/0.5/document.xml +10 -0
  101. slm/jinja2/slm/sitelog/xsd/geodesyml_0.4.xml +99 -0
  102. slm/jinja2/slm/sitelog/xsd/geodesyml_0.5.xml +112 -0
  103. slm/management/__init__.py +0 -0
  104. slm/management/commands/__init__.py +53 -0
  105. slm/management/commands/build_index.py +96 -0
  106. slm/management/commands/generate_sinex.py +675 -0
  107. slm/management/commands/head_from_index.py +541 -0
  108. slm/management/commands/import_archive.py +908 -0
  109. slm/management/commands/import_equipment.py +351 -0
  110. slm/management/commands/set_site.py +56 -0
  111. slm/management/commands/sitelog.py +144 -0
  112. slm/management/commands/synchronize.py +60 -0
  113. slm/management/commands/update_data_availability.py +167 -0
  114. slm/management/commands/validate_db.py +186 -0
  115. slm/management/commands/validate_gml.py +73 -0
  116. slm/map/__init__.py +1 -0
  117. slm/map/admin.py +5 -0
  118. slm/map/api/__init__.py +0 -0
  119. slm/map/api/edit/__init__.py +0 -0
  120. slm/map/api/edit/serializers.py +28 -0
  121. slm/map/api/edit/views.py +46 -0
  122. slm/map/api/public/__init__.py +0 -0
  123. slm/map/api/public/serializers.py +29 -0
  124. slm/map/api/public/views.py +64 -0
  125. slm/map/apps.py +7 -0
  126. slm/map/defines.py +53 -0
  127. slm/map/migrations/0001_initial.py +115 -0
  128. slm/map/migrations/__init__.py +0 -0
  129. slm/map/models.py +63 -0
  130. slm/map/static/slm/css/map.css +86 -0
  131. slm/map/static/slm/js/map.js +159 -0
  132. slm/map/templates/slm/map.html +374 -0
  133. slm/map/templates/slm/station/base.html +11 -0
  134. slm/map/templates/slm/station/edit.html +10 -0
  135. slm/map/templates/slm/top_nav.html +17 -0
  136. slm/map/templatetags/__init__.py +0 -0
  137. slm/map/templatetags/slm_map.py +18 -0
  138. slm/map/urls.py +25 -0
  139. slm/map/views.py +36 -0
  140. slm/middleware.py +29 -0
  141. slm/migrations/0001_alter_siteantenna_marker_enu_alter_sitelocation_llh_and_more.py +47 -0
  142. slm/migrations/0001_initial.py +4826 -0
  143. slm/migrations/0002_alter_dataavailability_site.py +22 -0
  144. slm/migrations/0003_remove_logentry_slm_logentr_site_lo_7a2af7_idx_and_more.py +80 -0
  145. slm/migrations/0004_alter_logentry_timestamp_and_more.py +25 -0
  146. slm/migrations/0005_alter_logentry_options_alter_logentry_section_and_more.py +46 -0
  147. slm/migrations/0006_alter_logentry_options_alter_logentry_index_together.py +24 -0
  148. slm/migrations/0007_alter_dataavailability_rate.py +23 -0
  149. slm/migrations/0008_alter_archiveindex_options_and_more.py +64 -0
  150. slm/migrations/0009_alter_archiveindex_end.py +21 -0
  151. slm/migrations/0010_alter_dataavailability_rinex_version_and_more.py +844 -0
  152. slm/migrations/0011_alter_siteidentification_fracture_spacing.py +33 -0
  153. slm/migrations/0012_alter_logentry_type.py +36 -0
  154. slm/migrations/0013_unpublishedfilesalert.py +48 -0
  155. slm/migrations/0014_sitelogpublished.py +48 -0
  156. slm/migrations/0015_alter_siteantenna_options_and_more.py +181 -0
  157. slm/migrations/0016_alter_antenna_description_alter_radome_description_and_more.py +42 -0
  158. slm/migrations/0017_alter_logentry_unique_together_and_more.py +54 -0
  159. slm/migrations/0018_afix_deleted.py +34 -0
  160. slm/migrations/0018_alter_siteantenna_options_and_more.py +244 -0
  161. slm/migrations/0019_remove_siteantenna_marker_enu_siteantenna_marker_une_and_more.py +101 -0
  162. slm/migrations/0020_alter_manufacturer_options.py +16 -0
  163. slm/migrations/0021_alter_siteform_report_type.py +23 -0
  164. slm/migrations/0022_rename_antcal_antenna_radome_slm_antcal_antenna_20827a_idx_and_more.py +297 -0
  165. slm/migrations/0023_archivedsitelog_gml_version_and_more.py +55 -0
  166. slm/migrations/0024_alter_agency_name_alter_agency_shortname.py +24 -0
  167. slm/migrations/0025_alter_archivedsitelog_log_format_and_more.py +61 -0
  168. slm/migrations/0026_alter_archivedsitelog_log_format_and_more.py +61 -0
  169. slm/migrations/0027_importalert_file_contents_importalert_findings_and_more.py +41 -0
  170. slm/migrations/0028_antenna_replaced_manufacturer_url_radome_replaced_and_more.py +46 -0
  171. slm/migrations/0029_manufacturer_full_name.py +17 -0
  172. slm/migrations/0030_alter_antenna_state_alter_radome_state_and_more.py +43 -0
  173. slm/migrations/__init__.py +0 -0
  174. slm/migrations/load_satellitesystems.py +27 -0
  175. slm/models/__init__.py +118 -0
  176. slm/models/about.py +14 -0
  177. slm/models/alerts.py +1204 -0
  178. slm/models/data.py +58 -0
  179. slm/models/equipment.py +229 -0
  180. slm/models/help.py +14 -0
  181. slm/models/index.py +428 -0
  182. slm/models/sitelog.py +4279 -0
  183. slm/models/system.py +723 -0
  184. slm/models/user.py +304 -0
  185. slm/parsing/__init__.py +786 -0
  186. slm/parsing/legacy/__init__.py +4 -0
  187. slm/parsing/legacy/binding.py +817 -0
  188. slm/parsing/legacy/parser.py +377 -0
  189. slm/parsing/xsd/__init__.py +34 -0
  190. slm/parsing/xsd/binding.py +86 -0
  191. slm/parsing/xsd/geodesyml/0.4/commonTypes.xsd +133 -0
  192. slm/parsing/xsd/geodesyml/0.4/contact.xsd +29 -0
  193. slm/parsing/xsd/geodesyml/0.4/dataStreams.xsd +129 -0
  194. slm/parsing/xsd/geodesyml/0.4/document.xsd +64 -0
  195. slm/parsing/xsd/geodesyml/0.4/equipment.xsd +427 -0
  196. slm/parsing/xsd/geodesyml/0.4/fieldMeasurement.xsd +170 -0
  197. slm/parsing/xsd/geodesyml/0.4/geodesyML.xsd +71 -0
  198. slm/parsing/xsd/geodesyml/0.4/geodeticEquipment.xsd +343 -0
  199. slm/parsing/xsd/geodesyml/0.4/geodeticMonument.xsd +147 -0
  200. slm/parsing/xsd/geodesyml/0.4/lineage.xsd +614 -0
  201. slm/parsing/xsd/geodesyml/0.4/localInterferences.xsd +131 -0
  202. slm/parsing/xsd/geodesyml/0.4/measurement.xsd +473 -0
  203. slm/parsing/xsd/geodesyml/0.4/monumentInfo.xsd +251 -0
  204. slm/parsing/xsd/geodesyml/0.4/observationSystem.xsd +429 -0
  205. slm/parsing/xsd/geodesyml/0.4/project.xsd +38 -0
  206. slm/parsing/xsd/geodesyml/0.4/quality.xsd +176 -0
  207. slm/parsing/xsd/geodesyml/0.4/referenceFrame.xsd +194 -0
  208. slm/parsing/xsd/geodesyml/0.4/siteLog.xsd +71 -0
  209. slm/parsing/xsd/geodesyml/0.5/commonTypes.xsd +133 -0
  210. slm/parsing/xsd/geodesyml/0.5/contact.xsd +29 -0
  211. slm/parsing/xsd/geodesyml/0.5/dataStreams.xsd +129 -0
  212. slm/parsing/xsd/geodesyml/0.5/document.xsd +64 -0
  213. slm/parsing/xsd/geodesyml/0.5/equipment.xsd +427 -0
  214. slm/parsing/xsd/geodesyml/0.5/fieldMeasurement.xsd +170 -0
  215. slm/parsing/xsd/geodesyml/0.5/geodesyML.xsd +71 -0
  216. slm/parsing/xsd/geodesyml/0.5/geodeticEquipment.xsd +343 -0
  217. slm/parsing/xsd/geodesyml/0.5/geodeticMonument.xsd +147 -0
  218. slm/parsing/xsd/geodesyml/0.5/lineage.xsd +614 -0
  219. slm/parsing/xsd/geodesyml/0.5/localInterferences.xsd +131 -0
  220. slm/parsing/xsd/geodesyml/0.5/measurement.xsd +473 -0
  221. slm/parsing/xsd/geodesyml/0.5/monumentInfo.xsd +306 -0
  222. slm/parsing/xsd/geodesyml/0.5/observationSystem.xsd +429 -0
  223. slm/parsing/xsd/geodesyml/0.5/project.xsd +38 -0
  224. slm/parsing/xsd/geodesyml/0.5/quality.xsd +176 -0
  225. slm/parsing/xsd/geodesyml/0.5/referenceFrame.xsd +194 -0
  226. slm/parsing/xsd/geodesyml/0.5/siteLog.xsd +73 -0
  227. slm/parsing/xsd/parser.py +116 -0
  228. slm/parsing/xsd/resolver.py +28 -0
  229. slm/receivers/__init__.py +11 -0
  230. slm/receivers/alerts.py +87 -0
  231. slm/receivers/cleanup.py +41 -0
  232. slm/receivers/event_loggers.py +175 -0
  233. slm/receivers/index.py +67 -0
  234. slm/settings/__init__.py +55 -0
  235. slm/settings/auth.py +15 -0
  236. slm/settings/ckeditor.py +14 -0
  237. slm/settings/debug.py +47 -0
  238. slm/settings/internationalization.py +12 -0
  239. slm/settings/logging.py +113 -0
  240. slm/settings/platform/__init__.py +0 -0
  241. slm/settings/platform/darwin.py +10 -0
  242. slm/settings/rest.py +21 -0
  243. slm/settings/root.py +152 -0
  244. slm/settings/routines.py +43 -0
  245. slm/settings/secrets.py +37 -0
  246. slm/settings/security.py +5 -0
  247. slm/settings/slm.py +188 -0
  248. slm/settings/static_templates.py +53 -0
  249. slm/settings/templates.py +29 -0
  250. slm/settings/uploads.py +8 -0
  251. slm/settings/urls.py +126 -0
  252. slm/settings/validation.py +196 -0
  253. slm/signals.py +250 -0
  254. slm/singleton.py +49 -0
  255. slm/static/rest_framework/css/bootstrap-tweaks.css +204 -0
  256. slm/static/rest_framework/css/bootstrap.min.css +7 -0
  257. slm/static/rest_framework/css/bootstrap.min.css.map +1 -0
  258. slm/static/rest_framework/css/default.css +82 -0
  259. slm/static/rest_framework/css/prettify.css +30 -0
  260. slm/static/rest_framework/docs/css/base.css +344 -0
  261. slm/static/rest_framework/docs/css/highlight.css +125 -0
  262. slm/static/rest_framework/docs/css/jquery.json-view.min.css +11 -0
  263. slm/static/rest_framework/docs/img/favicon.ico +0 -0
  264. slm/static/rest_framework/docs/img/grid.png +0 -0
  265. slm/static/rest_framework/docs/js/api.js +321 -0
  266. slm/static/rest_framework/docs/js/highlight.pack.js +2 -0
  267. slm/static/rest_framework/docs/js/jquery.json-view.min.js +7 -0
  268. slm/static/rest_framework/img/grid.png +0 -0
  269. slm/static/rest_framework/js/ajax-form.js +127 -0
  270. slm/static/rest_framework/js/bootstrap.bundle.min.js +7 -0
  271. slm/static/rest_framework/js/bootstrap.bundle.min.js.map +1 -0
  272. slm/static/rest_framework/js/bootstrap.min.js.map +1 -0
  273. slm/static/rest_framework/js/coreapi-0.1.1.js +2043 -0
  274. slm/static/rest_framework/js/csrf.js +52 -0
  275. slm/static/rest_framework/js/default.js +47 -0
  276. slm/static/rest_framework/js/jquery-3.5.1.min.js +2 -0
  277. slm/static/rest_framework/js/prettify-min.js +28 -0
  278. slm/static/slm/css/admin.css +3 -0
  279. slm/static/slm/css/defines.css +82 -0
  280. slm/static/slm/css/forms.css +1 -0
  281. slm/static/slm/css/style.css +1004 -0
  282. slm/static/slm/img/email-branding.png +0 -0
  283. slm/static/slm/img/favicon.ico +0 -0
  284. slm/static/slm/img/login-bg.jpg +0 -0
  285. slm/static/slm/img/slm-logo.svg +4 -0
  286. slm/static/slm/js/autocomplete.js +341 -0
  287. slm/static/slm/js/enums.js +322 -0
  288. slm/static/slm/js/fileIcons.js +30 -0
  289. slm/static/slm/js/form.js +404 -0
  290. slm/static/slm/js/formWidget.js +23 -0
  291. slm/static/slm/js/persistable.js +33 -0
  292. slm/static/slm/js/slm.js +1028 -0
  293. slm/static/slm/js/time24.js +212 -0
  294. slm/static_templates/slm/css/defines.css +26 -0
  295. slm/static_templates/slm/js/enums.js +28 -0
  296. slm/static_templates/slm/js/fileIcons.js +16 -0
  297. slm/static_templates/slm/js/urls.js +5 -0
  298. slm/templates/account/base.html +20 -0
  299. slm/templates/account/email/base.html +43 -0
  300. slm/templates/account/email/base_message.txt +7 -0
  301. slm/templates/account/email/email_confirmation_message.html +16 -0
  302. slm/templates/account/email/email_confirmation_message.txt +7 -0
  303. slm/templates/account/email/email_confirmation_signup_message.html +1 -0
  304. slm/templates/account/email/email_confirmation_signup_message.txt +1 -0
  305. slm/templates/account/email/email_confirmation_signup_subject.txt +1 -0
  306. slm/templates/account/email/email_confirmation_subject.txt +4 -0
  307. slm/templates/account/email/password_reset_key_message.html +28 -0
  308. slm/templates/account/email/password_reset_key_message.txt +9 -0
  309. slm/templates/account/email/password_reset_key_subject.txt +4 -0
  310. slm/templates/account/email/unknown_account_message.html +25 -0
  311. slm/templates/account/email/unknown_account_message.txt +12 -0
  312. slm/templates/account/email/unknown_account_subject.txt +4 -0
  313. slm/templates/account/login.html +67 -0
  314. slm/templates/account/logout.html +38 -0
  315. slm/templates/account/password_change.html +48 -0
  316. slm/templates/account/password_reset.html +51 -0
  317. slm/templates/account/password_reset_done.html +20 -0
  318. slm/templates/account/password_reset_from_key.html +52 -0
  319. slm/templates/account/password_reset_from_key_done.html +17 -0
  320. slm/templates/admin/base.html +7 -0
  321. slm/templates/messages.html +8 -0
  322. slm/templates/rest_framework/README +16 -0
  323. slm/templates/rest_framework/admin/detail.html +10 -0
  324. slm/templates/rest_framework/admin/dict_value.html +11 -0
  325. slm/templates/rest_framework/admin/list.html +32 -0
  326. slm/templates/rest_framework/admin/list_value.html +11 -0
  327. slm/templates/rest_framework/admin/simple_list_value.html +2 -0
  328. slm/templates/rest_framework/admin.html +282 -0
  329. slm/templates/rest_framework/api.html +3 -0
  330. slm/templates/rest_framework/base.html +334 -0
  331. slm/templates/rest_framework/docs/auth/basic.html +42 -0
  332. slm/templates/rest_framework/docs/auth/session.html +40 -0
  333. slm/templates/rest_framework/docs/auth/token.html +41 -0
  334. slm/templates/rest_framework/docs/document.html +37 -0
  335. slm/templates/rest_framework/docs/error.html +71 -0
  336. slm/templates/rest_framework/docs/index.html +55 -0
  337. slm/templates/rest_framework/docs/interact.html +57 -0
  338. slm/templates/rest_framework/docs/langs/javascript-intro.html +5 -0
  339. slm/templates/rest_framework/docs/langs/javascript.html +15 -0
  340. slm/templates/rest_framework/docs/langs/python-intro.html +3 -0
  341. slm/templates/rest_framework/docs/langs/python.html +13 -0
  342. slm/templates/rest_framework/docs/langs/shell-intro.html +3 -0
  343. slm/templates/rest_framework/docs/langs/shell.html +6 -0
  344. slm/templates/rest_framework/docs/link.html +113 -0
  345. slm/templates/rest_framework/docs/sidebar.html +78 -0
  346. slm/templates/rest_framework/filters/base.html +16 -0
  347. slm/templates/rest_framework/filters/ordering.html +17 -0
  348. slm/templates/rest_framework/filters/search.html +13 -0
  349. slm/templates/rest_framework/horizontal/checkbox.html +23 -0
  350. slm/templates/rest_framework/horizontal/checkbox_multiple.html +32 -0
  351. slm/templates/rest_framework/horizontal/dict_field.html +11 -0
  352. slm/templates/rest_framework/horizontal/fieldset.html +16 -0
  353. slm/templates/rest_framework/horizontal/form.html +6 -0
  354. slm/templates/rest_framework/horizontal/input.html +21 -0
  355. slm/templates/rest_framework/horizontal/list_field.html +11 -0
  356. slm/templates/rest_framework/horizontal/list_fieldset.html +13 -0
  357. slm/templates/rest_framework/horizontal/radio.html +42 -0
  358. slm/templates/rest_framework/horizontal/select.html +36 -0
  359. slm/templates/rest_framework/horizontal/select_multiple.html +38 -0
  360. slm/templates/rest_framework/horizontal/textarea.html +21 -0
  361. slm/templates/rest_framework/inline/checkbox.html +8 -0
  362. slm/templates/rest_framework/inline/checkbox_multiple.html +14 -0
  363. slm/templates/rest_framework/inline/dict_field.html +9 -0
  364. slm/templates/rest_framework/inline/fieldset.html +6 -0
  365. slm/templates/rest_framework/inline/form.html +8 -0
  366. slm/templates/rest_framework/inline/input.html +9 -0
  367. slm/templates/rest_framework/inline/list_field.html +9 -0
  368. slm/templates/rest_framework/inline/list_fieldset.html +3 -0
  369. slm/templates/rest_framework/inline/radio.html +25 -0
  370. slm/templates/rest_framework/inline/select.html +24 -0
  371. slm/templates/rest_framework/inline/select_multiple.html +25 -0
  372. slm/templates/rest_framework/inline/textarea.html +9 -0
  373. slm/templates/rest_framework/login.html +3 -0
  374. slm/templates/rest_framework/login_base.html +65 -0
  375. slm/templates/rest_framework/pagination/numbers.html +47 -0
  376. slm/templates/rest_framework/pagination/previous_and_next.html +21 -0
  377. slm/templates/rest_framework/raw_data_form.html +11 -0
  378. slm/templates/rest_framework/schema.js +3 -0
  379. slm/templates/rest_framework/vertical/checkbox.html +16 -0
  380. slm/templates/rest_framework/vertical/checkbox_multiple.html +30 -0
  381. slm/templates/rest_framework/vertical/dict_field.html +7 -0
  382. slm/templates/rest_framework/vertical/fieldset.html +13 -0
  383. slm/templates/rest_framework/vertical/form.html +6 -0
  384. slm/templates/rest_framework/vertical/input.html +17 -0
  385. slm/templates/rest_framework/vertical/list_field.html +7 -0
  386. slm/templates/rest_framework/vertical/list_fieldset.html +7 -0
  387. slm/templates/rest_framework/vertical/radio.html +40 -0
  388. slm/templates/rest_framework/vertical/select.html +34 -0
  389. slm/templates/rest_framework/vertical/select_multiple.html +31 -0
  390. slm/templates/rest_framework/vertical/textarea.html +17 -0
  391. slm/templates/slm/about.html +21 -0
  392. slm/templates/slm/alerts/alert.html +15 -0
  393. slm/templates/slm/alerts/geodesymlinvalid.html +8 -0
  394. slm/templates/slm/alerts/importalert.html +10 -0
  395. slm/templates/slm/alerts.html +18 -0
  396. slm/templates/slm/auth_menu.html +41 -0
  397. slm/templates/slm/base.html +195 -0
  398. slm/templates/slm/emails/alert_issued.html +31 -0
  399. slm/templates/slm/emails/alert_issued.txt +9 -0
  400. slm/templates/slm/emails/base.html +6 -0
  401. slm/templates/slm/emails/changes_rejected.txt +7 -0
  402. slm/templates/slm/emails/review_requested.txt +7 -0
  403. slm/templates/slm/forms/widgets/auto_complete.html +21 -0
  404. slm/templates/slm/forms/widgets/auto_complete_multiple.html +18 -0
  405. slm/templates/slm/forms/widgets/checkbox_multiple.html +6 -0
  406. slm/templates/slm/forms/widgets/inline_multi.html +1 -0
  407. slm/templates/slm/forms/widgets/splitdatetime.html +14 -0
  408. slm/templates/slm/forms/widgets/time24.html +37 -0
  409. slm/templates/slm/help.html +54 -0
  410. slm/templates/slm/messages.html +13 -0
  411. slm/templates/slm/new_site.html +88 -0
  412. slm/templates/slm/profile.html +57 -0
  413. slm/templates/slm/register.html +40 -0
  414. slm/templates/slm/reports/file_log.html +43 -0
  415. slm/templates/slm/reports/head_log.html +23 -0
  416. slm/templates/slm/reports/head_report.html +55 -0
  417. slm/templates/slm/reports/index_log.html +23 -0
  418. slm/templates/slm/reports/index_report.html +71 -0
  419. slm/templates/slm/station/alert.html +8 -0
  420. slm/templates/slm/station/alerts.html +19 -0
  421. slm/templates/slm/station/base.html +104 -0
  422. slm/templates/slm/station/download.html +87 -0
  423. slm/templates/slm/station/edit.html +283 -0
  424. slm/templates/slm/station/form.html +110 -0
  425. slm/templates/slm/station/log.html +18 -0
  426. slm/templates/slm/station/review.html +461 -0
  427. slm/templates/slm/station/upload.html +295 -0
  428. slm/templates/slm/station/uploads/attachment.html +20 -0
  429. slm/templates/slm/station/uploads/geodesyml.html +1 -0
  430. slm/templates/slm/station/uploads/image.html +27 -0
  431. slm/templates/slm/station/uploads/json.html +0 -0
  432. slm/templates/slm/station/uploads/legacy.html +77 -0
  433. slm/templates/slm/top_nav.html +14 -0
  434. slm/templates/slm/user_activity.html +16 -0
  435. slm/templates/slm/widgets/alert_scroll.html +135 -0
  436. slm/templates/slm/widgets/filelist.html +258 -0
  437. slm/templates/slm/widgets/legend.html +12 -0
  438. slm/templates/slm/widgets/log_scroll.html +88 -0
  439. slm/templates/slm/widgets/stationlist.html +233 -0
  440. slm/templatetags/__init__.py +0 -0
  441. slm/templatetags/jinja2.py +9 -0
  442. slm/templatetags/slm.py +459 -0
  443. slm/urls.py +148 -0
  444. slm/utils.py +299 -0
  445. slm/validators.py +297 -0
  446. slm/views.py +654 -0
  447. slm/widgets.py +134 -0
@@ -0,0 +1,351 @@
1
+ """
2
+ Station equipment including Antennas, Radomes and Receivers are coded by IGS and
3
+ those standard codes are used to uniquely identify equipment in site log files.
4
+
5
+ The full IGS changelog for equipment codes is recorded in rcvr_ant.tab_.
6
+
7
+ The SLM stores these codes in database tables, so it is possible to instantiate an
8
+ SLM with a different set of equipment codes. This command can be used to load codes
9
+ from another SLM or from the IGS by default:
10
+
11
+ * **antennas**: https://network.igs.org/api/public/antenna/
12
+ * **receivers**: https://network.igs.org/api/public/receiver/
13
+ * **radomes**: https://network.igs.org/api/public/radome/
14
+ * **manufacturers**: https://network.igs.org/api/public/manufacturer/
15
+
16
+ .. tip::
17
+
18
+ **import_equipment** can be run routinely to synchronize with upstream equipment
19
+ sources. No equipment will be removed if any site logs reference it in the database.
20
+ """
21
+
22
+ import typing as t
23
+
24
+ import requests
25
+ from django.db.models import Q
26
+ from django.utils.translation import gettext as _
27
+ from django_typer.completers import these_strings
28
+ from django_typer.management import TyperCommand, command, initialize
29
+ from typer import Context, Option
30
+ from typing_extensions import Annotated
31
+
32
+ from slm.defines import EquipmentState
33
+ from slm.models.equipment import Antenna, Manufacturer, Radome, Receiver
34
+
35
+
36
+ class Command(TyperCommand, rich_markup_mode="markdown", chain=True):
37
+ help = _(
38
+ "Import equipment (Antennas, Receivers and Radomes). Imports are made from "
39
+ "the IGS public network API by default. You may periodically run this to "
40
+ "synchronize with external equipment sources. These codings are used to "
41
+ "standardize references to equipment in site logs. For a full change log of "
42
+ "equipment encodings used by IGS, see: "
43
+ "https://files.igs.org/pub/station/general/rcvr_ant.tab"
44
+ )
45
+
46
+ suppressed_base_arguments = {
47
+ *TyperCommand.suppressed_base_arguments,
48
+ "version",
49
+ "pythonpath",
50
+ "settings"
51
+ }
52
+
53
+ remove: bool = False
54
+ synchronize: bool = True
55
+ slm: t.Optional[str] = None
56
+ states: t.List[t.Union[str, EquipmentState]] = [EquipmentState.ACTIVE.name]
57
+
58
+ manufacturers: t.Set[Manufacturer]
59
+ manufacturers_added: t.Set[Manufacturer]
60
+
61
+ antennas: t.Set[Antenna]
62
+ antennas_added: t.Set[Antenna]
63
+
64
+ receivers: t.Set[Receiver]
65
+ receivers_added: t.Set[Receiver]
66
+
67
+ radomes: t.Set[Radome]
68
+ radomes_added: t.Set[Radome]
69
+
70
+ @property
71
+ def params(self):
72
+ """Global equipment request params"""
73
+ return {"state": [st.value for st in self.states]}
74
+
75
+ @initialize(invoke_without_command=True)
76
+ def init(
77
+ self,
78
+ ctx: Context,
79
+ remove: Annotated[
80
+ bool,
81
+ Option(
82
+ "--remove",
83
+ help=_(
84
+ "Remove any equipment from the database not present in sources."
85
+ ),
86
+ ),
87
+ ] = remove,
88
+ no_synchronize: Annotated[
89
+ bool,
90
+ Option(
91
+ "--no-synchronize",
92
+ help=_(
93
+ "Do not synchronize ancillary equipment meta data with source(s)."
94
+ ),
95
+ ),
96
+ ] = synchronize,
97
+ slm: Annotated[
98
+ str,
99
+ Option(
100
+ help=_(
101
+ "The url to the SLM to pull equipment data from (if different than source defaults)."
102
+ )
103
+ ),
104
+ ] = "https://network.igs.org",
105
+ states: Annotated[
106
+ t.List[
107
+ str
108
+ ], # list of SiteLogFormat's does not work for some upstream reason
109
+ Option(
110
+ "--state",
111
+ metavar="STATE",
112
+ help=_("Only import equipment from these specified state(s)."),
113
+ shell_complete=these_strings([st.name for st in EquipmentState]),
114
+ ),
115
+ ] = states,
116
+ ):
117
+ self.remove = remove
118
+ self.synchronize = not no_synchronize
119
+ self.slm = slm
120
+ self.states = [EquipmentState(st) for st in states]
121
+ self.manufacturers = set()
122
+ self.manufacturers_added = set()
123
+ self.antennas = set()
124
+ self.antennas_added = set()
125
+ self.receivers = set()
126
+ self.receivers_added = set()
127
+ self.radomes = set()
128
+ self.radomes_added = set()
129
+
130
+ if not ctx.invoked_subcommand:
131
+ for cmd in [cmd for cmd in self.get_subcommand().children.values()]:
132
+ cmd()
133
+
134
+ def get_manufacturer(self, item):
135
+ manufacturer, created = Manufacturer.objects.get_or_create(
136
+ name=item.get("manufacturer", item)
137
+ )
138
+ self.manufacturers.add(manufacturer)
139
+ if created:
140
+ self.manufacturers_added.add(manufacturer)
141
+ return manufacturer
142
+
143
+ @command(help=_("Import manufacturers."))
144
+ def manufacturers(
145
+ self,
146
+ sources: Annotated[
147
+ t.List[str],
148
+ Option(
149
+ "--source", "-s", help=_("The source(s) to import manufacturers from.")
150
+ ),
151
+ ] = ["{slm}/api/public/manufacturer/"],
152
+ ):
153
+ sources = [src.format(slm=self.slm) for src in sources]
154
+ for src in sources:
155
+ manufacturers = requests.get(src, json=True).json()
156
+ for entry in manufacturers:
157
+ manufacturer, created = (
158
+ Manufacturer.objects.update_or_create
159
+ if self.synchronize
160
+ else Manufacturer.objects.get_or_create
161
+ )(
162
+ name=entry["name"],
163
+ defaults={
164
+ "full_name": entry.get("full_name", ""),
165
+ "url": entry.get("url", None),
166
+ },
167
+ )
168
+ self.manufacturers.add(manufacturer)
169
+ if created:
170
+ self.manufacturers_added.add(manufacturer)
171
+
172
+ self.secho(_(
173
+ "Added {added} manufacturers from {sources}."
174
+ ).format(
175
+ added=len(self.manufacturers_added),
176
+ sources=sources[0] if len(sources) == 1 else sources
177
+ ), fg="green")
178
+
179
+ if self.remove:
180
+ to_delete = Manufacturer.objects.filter(
181
+ ~Q(id__in=[mf.id for mf in self.manufacturers])
182
+ )
183
+ num_to_delete = to_delete.count()
184
+ if num_to_delete:
185
+ to_delete.delete()
186
+ self.secho(f"Removed {num_to_delete} manufacturers.", fg="red")
187
+
188
+ @command(help=_("Import antennas."))
189
+ def antennas(
190
+ self,
191
+ sources: Annotated[
192
+ t.List[str],
193
+ Option("--source", "-s", help=_("The source(s) to import antennas from.")),
194
+ ] = ["{slm}/api/public/antenna/"],
195
+ ):
196
+ replacements = {}
197
+ sources = [src.format(slm=self.slm) for src in sources]
198
+ for src in sources:
199
+ antennas = requests.get(src, params=self.params).json()
200
+ for entry in antennas:
201
+ antenna, created = (
202
+ Antenna.objects.update_or_create
203
+ if self.synchronize
204
+ else Antenna.objects.get_or_create
205
+ )(
206
+ model=entry["model"],
207
+ defaults={
208
+ "description": entry.get("description", ""),
209
+ "state": EquipmentState(
210
+ entry.get("state", EquipmentState.ACTIVE)
211
+ ),
212
+ "features": entry.get("features", None),
213
+ "reference_point": entry.get("reference_point", None),
214
+ "graphic": entry.get("graphic", ""),
215
+ },
216
+ )
217
+ replaced = entry.get("replaced", [])
218
+ if replaced:
219
+ replacements[antenna] = replaced
220
+ self.antennas.add(antenna)
221
+ if created:
222
+ self.antennas_added.add(antenna)
223
+
224
+ self.secho(_(
225
+ "Added {added} antennas from {sources}."
226
+ ).format(
227
+ added=len(self.antennas_added),
228
+ sources=sources[0] if len(sources) == 1 else sources
229
+ ), fg="green")
230
+
231
+ for ant, replaced in replacements.items():
232
+ ant.replaced.set(Antenna.objects.filter(model__in=replaced))
233
+
234
+ if self.remove:
235
+ to_delete = Antenna.objects.filter(
236
+ ~Q(id__in=[ant.id for ant in self.antennas])
237
+ )
238
+ num_to_delete = to_delete.count()
239
+ if num_to_delete:
240
+ to_delete.delete()
241
+ self.secho(f"Removed {num_to_delete} antennas.", fg="red")
242
+
243
+ @command(help=_("Import receivers."))
244
+ def receivers(
245
+ self,
246
+ sources: Annotated[
247
+ t.List[str],
248
+ Option("--source", "-s", help=_("The source(s) to import receivers from.")),
249
+ ] = ["{slm}/api/public/receiver/"],
250
+ ):
251
+ replacements = {}
252
+ sources = [src.format(slm=self.slm) for src in sources]
253
+ for source in sources:
254
+ source = source.format(slm=self.slm)
255
+ receivers = requests.get(source, params=self.params).json()
256
+ for entry in receivers:
257
+ receiver, created = (
258
+ Receiver.objects.update_or_create
259
+ if self.synchronize
260
+ else Receiver.objects.get_or_create
261
+ )(
262
+ model=entry["model"],
263
+ defaults={
264
+ "description": entry.get("description", ""),
265
+ "state": EquipmentState(
266
+ entry.get("state", EquipmentState.ACTIVE)
267
+ ),
268
+ },
269
+ )
270
+
271
+ replaced = entry.get("replaced", [])
272
+ if replaced:
273
+ replacements[receiver] = replaced
274
+
275
+ self.receivers.add(receiver)
276
+ if created:
277
+ self.receivers_added.add(receiver)
278
+
279
+ self.secho(_(
280
+ "Added {added} receivers from {sources}."
281
+ ).format(
282
+ added=len(self.receivers_added),
283
+ sources=sources[0] if len(sources) == 1 else sources
284
+ ), fg="green")
285
+
286
+ for ant, replaced in replacements.items():
287
+ ant.replaced.set(Receiver.objects.filter(model__in=replaced))
288
+
289
+ if self.remove:
290
+ to_delete = Receiver.objects.filter(
291
+ ~Q(id__in=[rec.id for rec in self.receivers])
292
+ )
293
+ num_to_delete = to_delete.count()
294
+ if num_to_delete:
295
+ to_delete.delete()
296
+ self.secho(f"Removed {num_to_delete} receivers.", fg="red")
297
+
298
+ @command(help=_("Import radomes."))
299
+ def radomes(
300
+ self,
301
+ sources: Annotated[
302
+ t.List[str],
303
+ Option("--source", "-s", help=_("The source(s) to import radomes from.")),
304
+ ] = ["{slm}/api/public/radome/"],
305
+ ):
306
+ replacements = {}
307
+ sources = [src.format(slm=self.slm) for src in sources]
308
+ for source in sources:
309
+ source = source.format(slm=self.slm)
310
+ radomes = requests.get(source, params=self.params).json()
311
+ for entry in radomes:
312
+ radome, created = (
313
+ Radome.objects.update_or_create
314
+ if self.synchronize
315
+ else Radome.objects.get_or_create
316
+ )(
317
+ model=entry["model"],
318
+ defaults={
319
+ "description": entry.get("description", ""),
320
+ "state": EquipmentState(
321
+ entry.get("state", EquipmentState.ACTIVE)
322
+ ),
323
+ },
324
+ )
325
+
326
+ replaced = entry.get("replaced", [])
327
+ if replaced:
328
+ replacements[radome] = replaced
329
+
330
+ self.radomes.add(radome)
331
+ if created:
332
+ self.radomes_added.add(radome)
333
+
334
+ self.secho(_(
335
+ "Added {added} radomes from {sources}."
336
+ ).format(
337
+ added=len(self.radomes_added),
338
+ sources=sources[0] if len(sources) == 1 else sources
339
+ ), fg="green")
340
+
341
+ for ant, replaced in replacements.items():
342
+ ant.replaced.set(Radome.objects.filter(model__in=replaced))
343
+
344
+ if self.remove:
345
+ to_delete = Radome.objects.filter(
346
+ ~Q(id__in=[rad.id for rad in self.radomes])
347
+ )
348
+ num_to_delete = to_delete.count()
349
+ if num_to_delete:
350
+ to_delete.delete()
351
+ self.secho(f"Removed {num_to_delete} radomes.", fg="red")
@@ -0,0 +1,56 @@
1
+ """
2
+ The `Django Sites framework <https://docs.djangoproject.com/en/stable/ref/contrib/sites/>`_
3
+ is used by the SLM and SLM extensions to store information about the organization name and
4
+ domain name being served by the current instance. This command populates that information
5
+ from SLM specific settings:
6
+
7
+ * SLM_SITE_NAME
8
+ * SLM_ORG_NAME
9
+
10
+ .. note::
11
+
12
+ The sites framework is typically used to support serving multiple domains off the same
13
+ software stack. In our experience it is usually preferable to serve separate domains
14
+ off customized software stacks because their requirements may differ substantially.
15
+ For example network.igs.org and slm.igs.org are both instances of the SLM with
16
+ different configurations.
17
+ """
18
+
19
+ from django.conf import settings
20
+ from django.contrib.sites.models import Site
21
+ from django.core.management import CommandError
22
+ from django.utils.translation import gettext as _
23
+ from django_typer.management import TyperCommand
24
+
25
+
26
+ class Command(TyperCommand):
27
+ help = _(
28
+ "Set the Django Site database object to reflect SLM_SITE_NAME and "
29
+ "SLM_ORG_NAME in settings."
30
+ )
31
+
32
+ suppressed_base_arguments = {
33
+ *TyperCommand.suppressed_base_arguments,
34
+ "version",
35
+ "pythonpath",
36
+ "settings",
37
+ "skip-checks",
38
+ }
39
+ requires_migrations_checks = False
40
+ requires_system_checks = []
41
+
42
+ def handle(self):
43
+ domain = getattr(settings, "SLM_SITE_NAME", None)
44
+ org = getattr(settings, "SLM_ORG_NAME", None)
45
+ if domain and org:
46
+ site = Site.objects.get_current()
47
+ site.domain = domain
48
+ site.name = org
49
+ site.save()
50
+ else:
51
+ raise CommandError(
52
+ _(
53
+ "To set the default Site object, both SLM_SITE_NAME and "
54
+ "SLM_ORG_NAME must be defined in settings."
55
+ )
56
+ )
@@ -0,0 +1,144 @@
1
+ """
2
+ Generate a serialized site log of the given format for a station's published or
3
+ unpublished head state. Optionally the file will be pulled from the index of archived
4
+ site log files.
5
+
6
+ It is usually more convenient to go through the web interface if you just want to view
7
+ a sitelog, but this command can be useful for entering into the debugger to troubleshoot
8
+ parts of this pipeline.
9
+ """
10
+
11
+ import typing as t
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+
15
+ from django.core.management import CommandError
16
+ from django.db.models import Q
17
+ from django.utils.translation import gettext as _
18
+ from django_typer.management import (
19
+ TyperCommand,
20
+ command,
21
+ initialize,
22
+ model_parser_completer,
23
+ )
24
+ from typer import Argument, Option
25
+ from typing_extensions import Annotated
26
+
27
+ from slm.api.serializers import SiteLogSerializer
28
+ from slm.defines import GeodesyMLVersion, SiteLogFormat
29
+ from slm.management.commands import EnumParser
30
+ from slm.models import ArchiveIndex, Site
31
+
32
+
33
+ class Command(TyperCommand):
34
+ help = _(
35
+ "Generate a site log in the specified format for the specified site "
36
+ "and print it to stdout. Optionally pull the log from the archive or "
37
+ "regenerate it based on given parameters."
38
+ )
39
+
40
+ suppressed_base_arguments = {
41
+ *TyperCommand.suppressed_base_arguments,
42
+ "version",
43
+ "pythonpath",
44
+ "settings",
45
+ "skip-checks",
46
+ }
47
+ requires_migrations_checks = False
48
+ requires_system_checks = []
49
+
50
+ site: Site
51
+ head: bool
52
+ archive: bool
53
+
54
+ @initialize()
55
+ def set_site(
56
+ self,
57
+ site: Annotated[
58
+ Site,
59
+ Argument(
60
+ **model_parser_completer(
61
+ Site, lookup_field="name", case_insensitive=True
62
+ ),
63
+ help=_("The name of the site."),
64
+ ),
65
+ ],
66
+ head: Annotated[
67
+ bool,
68
+ Option(
69
+ "--head", help=_("Generate the log from the head edits (unpublished).")
70
+ ),
71
+ ] = False,
72
+ archive: Annotated[ # todo, should be mutually exclusive with head
73
+ t.Optional[datetime],
74
+ Option(help=_("Pull from archive rather than generating.")),
75
+ ] = None,
76
+ ):
77
+ self.site = site
78
+ if head and archive:
79
+ raise CommandError("Cannot specify both --head and --archive.")
80
+ self.head = head
81
+ self.archive = archive
82
+
83
+ @command()
84
+ def legacy(self):
85
+ if self.archive:
86
+ index = ArchiveIndex.objects.filter(site=self.site).first()
87
+ log_file = index.files.filter(log_format=SiteLogFormat.LEGACY).first()
88
+ if not log_file:
89
+ raise CommandError(
90
+ _("Latest index of {site} does not have a legacy log file.").format(
91
+ site=self.site.name
92
+ )
93
+ )
94
+
95
+ self.stdout.write(Path(log_file.file.path).read_text())
96
+ else:
97
+ self.stdout.write(
98
+ SiteLogSerializer(
99
+ instance=self.site, published=None if self.head else True
100
+ ).format(SiteLogFormat.LEGACY)
101
+ )
102
+
103
+ @command()
104
+ def xml(
105
+ self,
106
+ gml_version: Annotated[
107
+ t.Optional[GeodesyMLVersion],
108
+ Option(
109
+ "--version",
110
+ metavar="VERSION",
111
+ parser=EnumParser(GeodesyMLVersion, "version"),
112
+ help=_("The Geodesy ML version. ({versions})").format(
113
+ versions=", ".join([str(gml.version) for gml in GeodesyMLVersion])
114
+ ),
115
+ ),
116
+ ] = None,
117
+ ):
118
+ if self.archive:
119
+ index = ArchiveIndex.objects.filter(site=self.site).first()
120
+ qry = Q(log_format=SiteLogFormat.GEODESY_ML)
121
+ qry &= Q(index__begin__lte=self.archive) & (
122
+ Q(index__end__gt=self.archive) | Q(index__end__isnull=True)
123
+ )
124
+ if gml_version:
125
+ qry &= Q(gml_version=gml_version)
126
+ log_file = index.files.filter(qry).first()
127
+ if not log_file:
128
+ raise CommandError(
129
+ _(
130
+ "Archive of {site} @ {archive} does not have a {version} log file."
131
+ ).format(
132
+ site=self.site.name,
133
+ archive=self.archive,
134
+ version=str(gml_version) or "GeodesyML",
135
+ )
136
+ )
137
+
138
+ self.stdout.write(Path(log_file.file.path).read_text())
139
+ else:
140
+ self.stdout.write(
141
+ SiteLogSerializer(
142
+ instance=self.site, published=None if self.head else True
143
+ ).format(SiteLogFormat.GEODESY_ML, version=gml_version)
144
+ )
@@ -0,0 +1,60 @@
1
+ """
2
+ To keep the SLM APIs performant some database state is
3
+ `denormalized <https://en.wikipedia.org/wiki/Denormalization>`_. In normal
4
+ operations this state is synchronized when it needs to be, but if for
5
+ whatever reason the denormalized state becomes unsynchronized, this command
6
+ can be manually invoked to force synchronization.
7
+
8
+ Some examples of denormalized state in the SLM include:
9
+
10
+ * Counts of validation flags
11
+ * Maximum alert levels for stations
12
+ * Sitelog status indicators (PUBLISHED/UNPUBLISHED) for stations.
13
+ """
14
+
15
+ import typing as t
16
+
17
+ from django.db import transaction
18
+ from django.db.models import Q
19
+ from django.utils.translation import gettext as _
20
+ from django_typer.management import TyperCommand, model_parser_completer
21
+ from typer import Argument
22
+ from typing_extensions import Annotated
23
+
24
+ from slm.models import Site
25
+
26
+
27
+ class Command(TyperCommand):
28
+ help = _(
29
+ "Synchronize all denormalized data - that is data that is cached for "
30
+ "performance reasons that may become out of sync if updates are "
31
+ "performed outside of normal request/response cycles."
32
+ )
33
+
34
+ suppressed_base_arguments = {
35
+ *TyperCommand.suppressed_base_arguments,
36
+ "version",
37
+ "pythonpath",
38
+ "settings",
39
+ }
40
+
41
+ def handle(
42
+ self,
43
+ sites: Annotated[
44
+ t.Optional[t.List[Site]],
45
+ Argument(
46
+ **model_parser_completer(
47
+ Site, lookup_field="name", case_insensitive=True
48
+ ),
49
+ help=_(
50
+ "The station(s) to synchronize, if unspecified, synchronize all "
51
+ "of them."
52
+ ),
53
+ ),
54
+ ] = None,
55
+ ):
56
+ with transaction.atomic():
57
+ qry = Q()
58
+ if sites:
59
+ qry = Q(pk__in=[site.id for site in sites])
60
+ Site.objects.filter(qry).synchronize_denormalized_state()