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