Compare commits
528 Commits
0.11
...
spacecruft
Author | SHA1 | Date |
---|---|---|
Jeff Moe | 5a7751ab30 | |
Jeff Moe | 8fd9db47e5 | |
Jeff Moe | e6a15bf889 | |
Vasilis Tsiligiannis | f6d1948e31 | |
George Sfoungaris | 78c52e7ead | |
Michał Drzał | a94d14ba71 | |
George Sfoungaris | aafb7a4c5e | |
George Sfoungaris | 24aa35d904 | |
George Sfoungaris | f847b783f6 | |
Alfredos-Panagiotis Damkalis | 5eff9c7e13 | |
George Sfoungaris | 8dbea1c47e | |
George Sfoungaris | 4eb3a9cce6 | |
George Sfoungaris | 0e2cfdea83 | |
George Sfoungaris | ae5bbb962d | |
Alfredos-Panagiotis Damkalis | 04b0605e09 | |
Vasilis Tsiligiannis | fb4a514693 | |
Alfredos-Panagiotis Damkalis | 0188846f0f | |
Alfredos-Panagiotis Damkalis | 97dc2b8d79 | |
Alfredos-Panagiotis Damkalis | aa45aee2a3 | |
Alfredos-Panagiotis Damkalis | 4e3e6c5902 | |
Alfredos-Panagiotis Damkalis | 7f1911082f | |
Alfredos-Panagiotis Damkalis | 56099d7055 | |
Alfredos-Panagiotis Damkalis | 03309529a2 | |
Alfredos-Panagiotis Damkalis | a13e2a0392 | |
Alfredos-Panagiotis Damkalis | 3b98e66dc8 | |
Alfredos-Panagiotis Damkalis | d7df35032d | |
Alfredos-Panagiotis Damkalis | 0dc188f41b | |
Alfredos-Panagiotis Damkalis | 9583d20c49 | |
Alfredos-Panagiotis Damkalis | 54327a8763 | |
Alfredos-Panagiotis Damkalis | aecfcda191 | |
Alfredos-Panagiotis Damkalis | 94b2594ee7 | |
Alfredos-Panagiotis Damkalis | b563760219 | |
Alfredos-Panagiotis Damkalis | 8870b425f5 | |
Alfredos-Panagiotis Damkalis | f63ac4294c | |
Alfredos-Panagiotis Damkalis | 3d4afd6f78 | |
Alfredos-Panagiotis Damkalis | 9d08f9d275 | |
Alfredos-Panagiotis Damkalis | 6d5f0d1ba8 | |
Vasilis Tsiligiannis | 13e045164e | |
Alfredos-Panagiotis Damkalis | 96ee6f2448 | |
Vasilis Tsiligiannis | f6829b658a | |
Vasilis Tsiligiannis | 6eda3ca996 | |
Alfredos-Panagiotis Damkalis | a4d6601fb1 | |
Alfredos-Panagiotis Damkalis | 842a6b3d77 | |
Alfredos-Panagiotis Damkalis | 56255a0ad5 | |
Alfredos-Panagiotis Damkalis | cdd2cee02f | |
Alfredos-Panagiotis Damkalis | 4117ef7fb4 | |
Alfredos-Panagiotis Damkalis | 6313b93b67 | |
Alfredos-Panagiotis Damkalis | e5c0f03339 | |
Alfredos-Panagiotis Damkalis | 7ae859baea | |
Alfredos-Panagiotis Damkalis | da58d39357 | |
Alfredos-Panagiotis Damkalis | 5e219954bb | |
Alfredos-Panagiotis Damkalis | e0cddccb85 | |
Alfredos-Panagiotis Damkalis | 26677db242 | |
Alfredos-Panagiotis Damkalis | 09bf23b376 | |
Fabian P. Schmidt | a8370bb06b | |
Alfredos-Panagiotis Damkalis | 2e13954fef | |
Alfredos-Panagiotis Damkalis | 1eb1526cd5 | |
Vasilis Tsiligiannis | ce07c90248 | |
Alfredos-Panagiotis Damkalis | 9ff314fe88 | |
Alfredos-Panagiotis Damkalis | 0f95cdec29 | |
deckbsd | 52d23bc7e0 | |
Fabian P. Schmidt | fffca10916 | |
Fabian P. Schmidt | 9f629bb715 | |
Fabian P. Schmidt | d674f10e70 | |
deckbsd | 51d9a3f499 | |
deckbsd | 8b641a89dc | |
Fabian P. Schmidt | ba950b6a34 | |
deckbsd | 24521a7f98 | |
Fabian P. Schmidt | 49ee559251 | |
Fabian P. Schmidt | 37daecf507 | |
Fabian P. Schmidt | 52147ade28 | |
Vasilis Tsiligiannis | 26c8f4d15f | |
Alfredos-Panagiotis Damkalis | 283cca5e40 | |
Alfredos-Panagiotis Damkalis | a175b59f1c | |
Alfredos-Panagiotis Damkalis | 69f4938908 | |
Alfredos-Panagiotis Damkalis | 84f754dc62 | |
Alfredos-Panagiotis Damkalis | 934a8eeccb | |
Papadeas Pierros | 51ec39a333 | |
Alfredos-Panagiotis Damkalis | 34f8c4e919 | |
Alfredos-Panagiotis Damkalis | 49eb53be30 | |
Alfredos-Panagiotis Damkalis | c8081962ae | |
Alfredos-Panagiotis Damkalis | 6341c26783 | |
Alfredos-Panagiotis Damkalis | 3c1eda6c2e | |
Alfredos-Panagiotis Damkalis | e7b0e56a17 | |
Alfredos-Panagiotis Damkalis | 3391fa8604 | |
Papadeas Pierros | f8ca235faa | |
Alfredos-Panagiotis Damkalis | 0d137863b3 | |
Alfredos-Panagiotis Damkalis | 06e2f032fe | |
Alfredos-Panagiotis Damkalis | 1513e998bb | |
Patrick Dohmen | 2081519af8 | |
Corey Shields | 79ca1834c9 | |
Vasilis Tsiligiannis | 33357e9207 | |
Alfredos-Panagiotis Damkalis | f046863123 | |
Corey Shields | 37b83bfa66 | |
Alfredos-Panagiotis Damkalis | d25436d594 | |
Alfredos-Panagiotis Damkalis | 804b69e442 | |
Corey Shields | 4141e275cc | |
Alfredos-Panagiotis Damkalis | 286c744141 | |
Corey Shields | 76c0d8f7b9 | |
Corey Shields | ad9f4f0d6f | |
Corey Shields | 292f1978db | |
Corey Shields | cbb169e5de | |
Corey Shields | c217f66b2d | |
Corey Shields | 33dd436cc1 | |
Alfredos-Panagiotis Damkalis | 3b05184d14 | |
Alfredos-Panagiotis Damkalis | 2f11e6b8e8 | |
Alfredos-Panagiotis Damkalis | 418fe5ab3d | |
Alfredos-Panagiotis Damkalis | c75b491b9b | |
Alfredos-Panagiotis Damkalis | f4ea975e0c | |
Alfredos-Panagiotis Damkalis | af2d04a33f | |
Alfredos-Panagiotis Damkalis | d1eab762fa | |
Alfredos-Panagiotis Damkalis | 0d81ecbe77 | |
Alfredos-Panagiotis Damkalis | ce6a5ebf4c | |
Vasilis Tsiligiannis | 79ebbe219e | |
Corey Shields | 967c05b212 | |
Alfredos-Panagiotis Damkalis | cc073daf45 | |
Alfredos-Panagiotis Damkalis | 29c0d3654b | |
Alfredos-Panagiotis Damkalis | 4ea55fa1cc | |
Alfredos-Panagiotis Damkalis | f6fd93f53e | |
Alfredos-Panagiotis Damkalis | 5426167d5b | |
Alfredos-Panagiotis Damkalis | a97f9666db | |
Alfredos-Panagiotis Damkalis | 2805acde27 | |
Alfredos-Panagiotis Damkalis | 00a91ba947 | |
Alfredos-Panagiotis Damkalis | efd5dd1a96 | |
Corey Shields | 2b17d32a67 | |
Alfredos-Panagiotis Damkalis | 8adda93204 | |
Alfredos-Panagiotis Damkalis | afda478ccc | |
Alfredos-Panagiotis Damkalis | 443668b90c | |
Alfredos-Panagiotis Damkalis | 2b3282792d | |
Alfredos-Panagiotis Damkalis | 8ca9004fc9 | |
Alfredos-Panagiotis Damkalis | 0e6c120cc9 | |
Alfredos-Panagiotis Damkalis | 4aed383ed0 | |
Alfredos-Panagiotis Damkalis | 87ac2f9dc9 | |
Alfredos-Panagiotis Damkalis | 4b8239739a | |
Alfredos-Panagiotis Damkalis | 1b366d1722 | |
Alfredos-Panagiotis Damkalis | f1d885f172 | |
Corey Shields | 131831a811 | |
Corey Shields | bb24e771d6 | |
Vasilis Tsiligiannis | 0f09f58629 | |
Vasilis Tsiligiannis | fd337e9dd0 | |
Vasilis Tsiligiannis | 8107ee4460 | |
Vasilis Tsiligiannis | 98acbd2640 | |
Vasilis Tsiligiannis | 79382a5c2f | |
Vasilis Tsiligiannis | d92e655cb5 | |
Vasilis Tsiligiannis | 648db98037 | |
Vasilis Tsiligiannis | a114826f80 | |
Vasilis Tsiligiannis | 3d96fe1e41 | |
Corey Shields | 5e03f7c759 | |
Corey Shields | bec7469dc2 | |
Corey Shields | 8ec3b93aba | |
Corey Shields | e5ddc38e96 | |
Corey Shields | d153ece5ef | |
Corey Shields | bcee356c24 | |
Corey Shields | 46eb5aa8c8 | |
Corey Shields | 17f4ab100b | |
Corey Shields | a1aafce76b | |
Corey Shields | 0fc7107172 | |
Corey Shields | 46883750e4 | |
Vasilis Tsiligiannis | 18ff90735e | |
Corey Shields | 83f9b07e21 | |
Vasilis Tsiligiannis | cc422353e7 | |
Vasilis Tsiligiannis | 1abbf425ef | |
Vasilis Tsiligiannis | 1da488642a | |
Vasilis Tsiligiannis | 7629b8f762 | |
Vasilis Tsiligiannis | 9abde9b765 | |
Vasilis Tsiligiannis | 99f3a5fcc2 | |
deckbsd | c07696c80e | |
Papadeas Pierros | 9e8e1b169f | |
Vasilis Tsiligiannis | 8d60968729 | |
deckbsd | 3ccc3ef0fd | |
Corey Shields | c7772f4c78 | |
Vasilis Tsiligiannis | a3866f180b | |
Vasilis Tsiligiannis | fce52ec637 | |
Vasilis Tsiligiannis | 1f2dc4b75f | |
Vasilis Tsiligiannis | 6680a7fe12 | |
Vasilis Tsiligiannis | 6da1afd742 | |
Vasilis Tsiligiannis | b98cb87f4d | |
Vasilis Tsiligiannis | d435eef416 | |
Vasilis Tsiligiannis | 816eba90c2 | |
Patrick Dohmen | 83f383d851 | |
Corey Shields | b44b618c04 | |
Corey Shields | 2400c6b4f6 | |
Corey Shields | a653f0697c | |
Vasilis Tsiligiannis | 08cd629c05 | |
Fabian P. Schmidt | 3ed0d750db | |
Pierros Papadeas | 2835df9553 | |
Alfredos-Panagiotis Damkalis | 8e52ab8e8d | |
Vasilis Tsiligiannis | f85273a592 | |
Alfredos-Panagiotis Damkalis | f53d240f5b | |
Alfredos-Panagiotis Damkalis | 387affac12 | |
Alfredos-Panagiotis Damkalis | 2b594d427c | |
Alfredos-Panagiotis Damkalis | b31d40809f | |
Alfredos-Panagiotis Damkalis | ed0a22503d | |
Vasilis Tsiligiannis | 5406261482 | |
Vasilis Tsiligiannis | 8561037130 | |
Vasilis Tsiligiannis | 34c7e4fd03 | |
Alfredos-Panagiotis Damkalis | 815d56a6f8 | |
Vasilis Tsiligiannis | e29e8eb5a4 | |
Vasilis Tsiligiannis | 30e0c27461 | |
Vasilis Tsiligiannis | e6a76cf1a7 | |
Vasilis Tsiligiannis | 13c39be360 | |
Vasilis Tsiligiannis | b0aebd41b9 | |
Vasilis Tsiligiannis | 9a39a70722 | |
Vasilis Tsiligiannis | 53ea7728e2 | |
Vasilis Tsiligiannis | 6af7d06a74 | |
Vasilis Tsiligiannis | b457e7460d | |
Patrick Dohmen | cb0b00e304 | |
Alfredos-Panagiotis Damkalis | 8e2f52a59f | |
Patrick Dohmen | 1e9b695569 | |
Alfredos-Panagiotis Damkalis | 09e630890f | |
Alfredos-Panagiotis Damkalis | a841bd48d5 | |
Alfredos-Panagiotis Damkalis | 63e1c50f8d | |
Alfredos-Panagiotis Damkalis | e38fa63079 | |
Alfredos-Panagiotis Damkalis | 87d5329c36 | |
Patrick Dohmen | 6dd9bff360 | |
Patrick Dohmen | 2ef400acf9 | |
Vasilis Tsiligiannis | 31320f739c | |
Vasilis Tsiligiannis | 2b714a754e | |
Alfredos-Panagiotis Damkalis | 6a12f76a26 | |
Alfredos-Panagiotis Damkalis | ae48ac7008 | |
Alfredos-Panagiotis Damkalis | 74fd41b2a8 | |
Alfredos-Panagiotis Damkalis | e27fbb09b9 | |
Pierros Papadeas | 5c01c5d72b | |
Vasilis Tsiligiannis | b51e6f4792 | |
Vasilis Tsiligiannis | 498fa704a3 | |
Vasilis Tsiligiannis | e2086c72b8 | |
Alfredos-Panagiotis Damkalis | 45eb948f57 | |
Alfredos-Panagiotis Damkalis | 816ab8cd49 | |
Alfredos-Panagiotis Damkalis | 09bb5b0875 | |
Alfredos-Panagiotis Damkalis | dd10892eaf | |
Vasilis Tsiligiannis | 37cc41d86c | |
Corey Shields | 0dbb1df42d | |
Corey Shields | 2e25763185 | |
Corey Shields | b5dd2eb4ea | |
Corey Shields | 3af17917ba | |
Corey Shields | eb859b7a44 | |
Pierros Papadeas | fa32a0ebb4 | |
Corey Shields | d6f6ff0c2e | |
Vasilis Tsiligiannis | cb705d3c1b | |
Vasilis Tsiligiannis | 613ef7cdbb | |
Vasilis Tsiligiannis | db9457776c | |
Corey Shields | 3d1c504e1d | |
Corey Shields | 402ec942ab | |
Corey Shields | 0600d2a090 | |
Corey Shields | b002ca6e37 | |
Corey Shields | b7e82848d5 | |
Corey Shields | 5395122ac7 | |
Corey Shields | aa5eaaad74 | |
Pierros Papadeas | 87ab59c738 | |
Alfredos-Panagiotis Damkalis | f18140e153 | |
Alfredos-Panagiotis Damkalis | e1b859887f | |
Alfredos-Panagiotis Damkalis | e2b916f3ed | |
Alfredos-Panagiotis Damkalis | ce1c189f02 | |
Corey Shields | fda7defc8b | |
Alfredos-Panagiotis Damkalis | 00e9cb1cc3 | |
Corey Shields | 75ad7ecd35 | |
Alfredos-Panagiotis Damkalis | 2c97ff4f58 | |
Alfredos-Panagiotis Damkalis | 41cb47f9ff | |
Corey Shields | 771aa22ecd | |
Corey Shields | 931e0893d5 | |
Corey Shields | 567df49a8f | |
Corey Shields | 46c73722c4 | |
Corey Shields | 07a4904ff8 | |
Pierros Papadeas | 1fd8406ab5 | |
Pierros Papadeas | 01c581ea8a | |
Alfredos-Panagiotis Damkalis | ed6ac366fb | |
Corey Shields | f6d2bcdc93 | |
Corey Shields | 44daf627be | |
Corey Shields | 8b90fd1ff9 | |
Corey Shields | f05658476f | |
Corey Shields | 3149c91e56 | |
Corey Shields | b198e188d3 | |
Pierros Papadeas | 9ad52c8da8 | |
Corey Shields | 488fd9d7b4 | |
Corey Shields | 5d0df9ac8f | |
Corey Shields | 2f51df5337 | |
Pierros Papadeas | 657260ed9f | |
deckbsd | 72b1a4abad | |
Pierros Papadeas | 86d1bd6abc | |
Pierros Papadeas | 96e971d614 | |
deckbsd | 01d9602693 | |
Alfredos-Panagiotis Damkalis | 7938239120 | |
Corey Shields | 0ba46f0913 | |
Corey Shields | 2b80a3a88f | |
Corey Shields | b41b77be94 | |
deckbsd | 768779ec6a | |
deckbsd | ee7c8fdcef | |
Pierros Papadeas | c444bf3645 | |
deckbsd | 461c8af026 | |
Corey Shields | c1d57a79cb | |
Corey Shields | 95e89dc4b5 | |
Corey Shields | 508a22acf0 | |
Pierros Papadeas | c17c7104f8 | |
Pierros Papadeas | a1fe08f352 | |
Corey Shields | ff3d88001e | |
deckbsd | faa38e3960 | |
deckbsd | f9baf45aa1 | |
Pierros Papadeas | 1f6f74be64 | |
deckbsd | ead89b6d03 | |
deckbsd | f0bee6eda9 | |
deckbsd | 60f2356c73 | |
Corey Shields | b7d2392c35 | |
Corey Shields | 4c3147d4c5 | |
deckbsd | 05d4f8a705 | |
Corey Shields | cbd0ec096a | |
Corey Shields | 122baae403 | |
Corey Shields | 68e1e20a4e | |
deckbsd | 999bab50c6 | |
Corey Shields | a7141c5b30 | |
deckbsd | 63a93fa1ed | |
deckbsd | 5122729752 | |
deckbsd | 317eed5a34 | |
Alfredos-Panagiotis Damkalis | 8c319bfe62 | |
Alfredos-Panagiotis Damkalis | f5e61c0124 | |
Alfredos-Panagiotis Damkalis | 504392d1e7 | |
Alfredos-Panagiotis Damkalis | 6094847432 | |
Alfredos-Panagiotis Damkalis | aef8d5c33b | |
Alfredos-Panagiotis Damkalis | 118cb7cadc | |
Alfredos-Panagiotis Damkalis | 39befe0dfc | |
Pierros Papadeas | 980ef8abe2 | |
Pierros Papadeas | a0d398f129 | |
Pierros Papadeas | ef1e315984 | |
Alfredos-Panagiotis Damkalis | 67f229d2ed | |
Alfredos-Panagiotis Damkalis | 20153300cf | |
Alfredos-Panagiotis Damkalis | 412c2aa085 | |
Pierros Papadeas | c98aea69fc | |
deckbsd | ef1c4313aa | |
Pierros Papadeas | d8aef58025 | |
Alfredos-Panagiotis Damkalis | 4891efd75e | |
Alfredos-Panagiotis Damkalis | 6a81937465 | |
Alfredos-Panagiotis Damkalis | 90d860377c | |
Alfredos-Panagiotis Damkalis | 1736abf6b2 | |
Pierros Papadeas | 9f76d3cb25 | |
Vasilis Tsiligiannis | b7d2d4ff95 | |
Vasilis Tsiligiannis | ddf62bf660 | |
Alfredos-Panagiotis Damkalis | fee2c8683e | |
Vasilis Tsiligiannis | 79efc9a1c3 | |
Vasilis Tsiligiannis | 2e59bc3acc | |
Alfredos-Panagiotis Damkalis | a088e5d03b | |
Alfredos-Panagiotis Damkalis | 4ba7b8cfa9 | |
Alfredos-Panagiotis Damkalis | 1f0f0767f9 | |
deckbsd | 1d1c72edf4 | |
Vasilis Tsiligiannis | d77e9b0f86 | |
Vasilis Tsiligiannis | e4b2161f82 | |
Vasilis Tsiligiannis | b9b1783434 | |
Vasilis Tsiligiannis | c06fe77cc1 | |
Vasilis Tsiligiannis | e9334d89e1 | |
Alfredos-Panagiotis Damkalis | 3b2f41bded | |
Vasilis Tsiligiannis | 1664f68068 | |
Vasilis Tsiligiannis | f656347aed | |
Vasilis Tsiligiannis | b74d8ba581 | |
Vasilis Tsiligiannis | 7d862342f1 | |
Vasilis Tsiligiannis | 80a5c561f1 | |
Vasilis Tsiligiannis | 4435d64a8d | |
Alfredos-Panagiotis Damkalis | fcf1e0852e | |
Vasilis Tsiligiannis | a7af3b8c69 | |
Vasilis Tsiligiannis | b60e7182d1 | |
Vasilis Tsiligiannis | 091c61dd65 | |
Alfredos-Panagiotis Damkalis | e6d0f4b417 | |
Alfredos-Panagiotis Damkalis | 36a339f6a3 | |
Alfredos-Panagiotis Damkalis | 9b5da399fa | |
Alfredos-Panagiotis Damkalis | 1ec0da8355 | |
Fabian P. Schmidt | 80558c4c28 | |
Fabian P. Schmidt | e681e825e2 | |
Fabian P. Schmidt | f3c395a795 | |
Alfredos-Panagiotis Damkalis | 3f4e5fe03f | |
Alfredos-Panagiotis Damkalis | 6d86d9fb23 | |
Alfredos-Panagiotis Damkalis | d0440e4155 | |
Alfredos-Panagiotis Damkalis | 4b721a4488 | |
Vasilis Tsiligiannis | b63487e8c4 | |
Vasilis Tsiligiannis | f451f255e7 | |
Vasilis Tsiligiannis | d7ebe23830 | |
Vasilis Tsiligiannis | c764b46dff | |
Vasilis Tsiligiannis | 116033384c | |
Vasilis Tsiligiannis | df6c9b0304 | |
Vasilis Tsiligiannis | 632f951531 | |
Fabian P. Schmidt | 97dc4e0652 | |
Vasilis Tsiligiannis | 4a4f96f352 | |
Vasilis Tsiligiannis | c231b9eaa1 | |
Vasilis Tsiligiannis | 5c8c9fb663 | |
Fabian P. Schmidt | 4278c6caa0 | |
deckbsd | 2967e608c3 | |
Fabian P. Schmidt | f72bd9a3f3 | |
deckbsd | 062152aeec | |
Alfredos-Panagiotis Damkalis | 34f2641bef | |
deckbsd | fbd0fbf34e | |
Corey Shields | 72e5d728a2 | |
Corey Shields | e7e400e49a | |
Corey Shields | 8cae425ac9 | |
Vasilis Tsiligiannis | 8710a2df3b | |
Alfredos-Panagiotis Damkalis | 494351e2e7 | |
Vasilis Tsiligiannis | 9f159a2ede | |
Vasilis Tsiligiannis | 95d08f6fa4 | |
Vasilis Tsiligiannis | 6254319426 | |
Vasilis Tsiligiannis | 98b2513ec8 | |
Vasilis Tsiligiannis | 9a5f58864e | |
Vasilis Tsiligiannis | 59c3078872 | |
Vasilis Tsiligiannis | 9fdb19d112 | |
Alfredos-Panagiotis Damkalis | b852ed8372 | |
Alfredos-Panagiotis Damkalis | 281913e9f6 | |
Alfredos-Panagiotis Damkalis | ef40817f61 | |
Patrick Dohmen | 8c340d9e78 | |
Alfredos-Panagiotis Damkalis | e691ddb79e | |
Vasilis Tsiligiannis | 24a353fde0 | |
Vasilis Tsiligiannis | 7ebc04e534 | |
Vasilis Tsiligiannis | 25059fbeac | |
Vasilis Tsiligiannis | 5c4e94ef76 | |
Vasilis Tsiligiannis | 8f3666c2a6 | |
Vasilis Tsiligiannis | 456767786b | |
Vasilis Tsiligiannis | ada835d85d | |
Vasilis Tsiligiannis | bea4b1c9a1 | |
Corey Shields | 3e24773476 | |
Vasilis Tsiligiannis | f5e95c7af7 | |
Vasilis Tsiligiannis | fc90760e7c | |
Vasilis Tsiligiannis | 70ad7ed3de | |
Vasilis Tsiligiannis | 572ac24d44 | |
Vasilis Tsiligiannis | b288a1249d | |
Vasilis Tsiligiannis | 18e377cad5 | |
Corey Shields | 45cf720f99 | |
Corey Shields | cacef2a91e | |
muarachmann | c01cdb1aa7 | |
Corey Shields | 61f9137aae | |
Corey Shields | 6920bbab03 | |
Corey Shields | f80f207c78 | |
Corey Shields | f1420d3e9e | |
Corey Shields | d71114130a | |
Corey Shields | 40460c47bb | |
Corey Shields | fc8f9a8c35 | |
Corey Shields | 9359532df9 | |
Corey Shields | f9336eb0b4 | |
Corey Shields | b0bfe0eea7 | |
Corey Shields | 8271351729 | |
Corey Shields | 91d8ba20bd | |
Corey Shields | b704454486 | |
deckbsd | 965a4f3658 | |
Vasilis Tsiligiannis | 70ffe07f22 | |
Vasilis Tsiligiannis | 5abb3553f9 | |
Vasilis Tsiligiannis | 26662074e5 | |
Vasilis Tsiligiannis | 3404db012c | |
Vasilis Tsiligiannis | ddccf3146e | |
Vasilis Tsiligiannis | f2538355ab | |
Corey Shields | f6e61f158f | |
Corey Shields | 3596ebfb9f | |
Corey Shields | 9989efb453 | |
Corey Shields | 3ce647e374 | |
Corey Shields | 389194b076 | |
Corey Shields | c376e08826 | |
Corey Shields | adfc08bb79 | |
Corey Shields | 6bc64e9217 | |
Corey Shields | fe1f989d7b | |
Corey Shields | 0d4701b100 | |
Corey Shields | f7c4df84f1 | |
Corey Shields | 0da7589c1d | |
Corey Shields | fca0c1d419 | |
Corey Shields | 678de105dd | |
Corey Shields | ec07c59263 | |
Corey Shields | a8a0dc4aa6 | |
Corey Shields | 8b6410ba2c | |
Corey Shields | f90fe277d5 | |
Corey Shields | 2ec2fd2a4d | |
Corey Shields | dcccb1770e | |
Corey Shields | a697548a56 | |
Corey Shields | 8a3ebb3caf | |
Pierros Papadeas | 8a51d27f65 | |
Corey Shields | b7ec119853 | |
Pierros Papadeas | 853381a5dc | |
Pierros Papadeas | d1fb393c79 | |
Pierros Papadeas | 52c91d46a7 | |
Pierros Papadeas | 48ea435093 | |
Fabian P. Schmidt | 203f9a7258 | |
Fabian P. Schmidt | 1dfdbc8cc3 | |
Poonam Mishra | 9a0ef30e03 | |
Corey Shields | 248390a760 | |
Corey Shields | 8734748e50 | |
Corey Shields | dc06369f9a | |
Corey Shields | f734919b61 | |
Corey Shields | f0c0c9bc8b | |
Corey Shields | ff56cb6431 | |
Fabian P. Schmidt | 723fa19ebb | |
Vasilis Tsiligiannis | 3872105dac | |
Corey Shields | d4fb5189d1 | |
Pierros Papadeas | 5433a7a67b | |
Vasilis Tsiligiannis | 1d85a64e6e | |
Vasilis Tsiligiannis | a7e80bb4e5 | |
Vasilis Tsiligiannis | f59efae039 | |
Vasilis Tsiligiannis | 6da2fadf94 | |
Vasilis Tsiligiannis | 4bb87e2e6a | |
Vasilis Tsiligiannis | 084694b02c | |
Vasilis Tsiligiannis | c88a139f89 | |
Vasilis Tsiligiannis | 6ba9a0428a | |
Vasilis Tsiligiannis | 811b3a8a8f | |
Vasilis Tsiligiannis | 826f7bbe9f | |
Vasilis Tsiligiannis | be4e2fd1fc | |
Vasilis Tsiligiannis | 45abdcd33a | |
Vasilis Tsiligiannis | cb2c4899a9 | |
Vasilis Tsiligiannis | eee14e66e8 | |
Fabian P. Schmidt | f141ce2580 | |
Alfredos-Panagiotis Damkalis | d3501fd8f3 | |
Alfredos-Panagiotis Damkalis | 756aca8ad9 | |
Vasilis Tsiligiannis | 655f98f39c | |
Vasilis Tsiligiannis | 445b9b0a00 | |
Vasilis Tsiligiannis | c6426139a9 | |
Vasilis Tsiligiannis | 140180b6b3 | |
Vasilis Tsiligiannis | 2df9d69d9d | |
Vasilis Tsiligiannis | a8993036d1 | |
Vasilis Tsiligiannis | 3ffc7b7615 | |
Vasilis Tsiligiannis | 1ebedf7f90 | |
Vasilis Tsiligiannis | e761c319b6 | |
Vasilis Tsiligiannis | f0856840f7 | |
Vasilis Tsiligiannis | 8c3debcc43 | |
Vasilis Tsiligiannis | 369f51cddb | |
Alfredos-Panagiotis Damkalis | f3977c27ea | |
Vasilis Tsiligiannis | 448b48e61c | |
Vasilis Tsiligiannis | 28a7111a1f | |
Corey Shields | 5e5296f154 | |
Corey Shields | ff21847b52 | |
Alfredos-Panagiotis Damkalis | 5b7814000c | |
Alfredos-Panagiotis Damkalis | d786ce72bd | |
Corey Shields | f43c387e2d | |
Kevin Pak | 4c6ee89cd7 | |
Vasilis Tsiligiannis | a2401fe1e5 | |
Vasilis Tsiligiannis | d574b65cfd | |
Vasilis Tsiligiannis | 7b1315cb9d | |
Vasilis Tsiligiannis | efa30be189 | |
Vasilis Tsiligiannis | adf4b813a8 | |
Alfredos-Panagiotis Damkalis | 30e2b6ef9e | |
Alfredos-Panagiotis Damkalis | c2e378da4e | |
Patrick Dohmen | cc4e9ef141 |
|
@ -5,6 +5,8 @@ __pycache__
|
|||
env
|
||||
.env
|
||||
.cache
|
||||
*.egg-info
|
||||
*.DS_Store
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
@ -15,6 +17,10 @@ pip-log.txt
|
|||
.tox
|
||||
nosetests.xml
|
||||
|
||||
# build artifacts
|
||||
build
|
||||
dist
|
||||
|
||||
# Sqlite
|
||||
*.db
|
||||
*.sqlite
|
||||
|
@ -23,8 +29,17 @@ nosetests.xml
|
|||
# Media & Static
|
||||
media
|
||||
/staticfiles/*
|
||||
/db/static/lib/
|
||||
node_modules
|
||||
yarn-error.log
|
||||
|
||||
# Celery
|
||||
celerybeat-schedule
|
||||
|
||||
# Documentation
|
||||
/docs/_build/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
|
|
262
.gitlab-ci.yml
262
.gitlab-ci.yml
|
@ -1,64 +1,165 @@
|
|||
variables:
|
||||
GITLAB_CI_IMAGE_ALPINE: 'alpine:3.9'
|
||||
GITLAB_CI_IMAGE_DOCKER: 'docker:20.10.6'
|
||||
GITLAB_CI_IMAGE_NODE: 'node:13.12'
|
||||
GITLAB_CI_IMAGE_PYTHON: 'python:3.9.12'
|
||||
GITLAB_CI_IMAGE_OPENAPI_GENERATOR_CLI: 'openapitools/openapi-generator-cli:v5.3.0'
|
||||
GITLAB_CI_IMAGE_SENTRY_CLI: 'getsentry/sentry-cli'
|
||||
GITLAB_CI_PYPI_DOCKER_COMPOSE: 'docker-compose~=1.23.0'
|
||||
GITLAB_CI_PYPI_TOX: 'tox~=3.20.0'
|
||||
stages:
|
||||
- schema
|
||||
- api
|
||||
- static
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
- sentry_release
|
||||
- trigger
|
||||
- security
|
||||
|
||||
# 'schema' stage
|
||||
schema:
|
||||
stage: schema
|
||||
needs: []
|
||||
image: ${GITLAB_CI_IMAGE_PYTHON}
|
||||
script:
|
||||
- pip install --no-cache-dir --no-deps -r "requirements.txt" --force-reinstall .
|
||||
- >-
|
||||
./manage.py spectacular
|
||||
--file satnogs-db-api-client/api-schema.yml
|
||||
--validate
|
||||
--fail-on-warn
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
paths:
|
||||
- satnogs-db-api-client
|
||||
|
||||
# 'api' stage
|
||||
api:
|
||||
stage: api
|
||||
needs:
|
||||
- job: schema
|
||||
artifacts: true
|
||||
image: ${GITLAB_CI_IMAGE_OPENAPI_GENERATOR_CLI}
|
||||
script:
|
||||
- >-
|
||||
docker-entrypoint.sh
|
||||
generate
|
||||
-i satnogs-db-api-client/api-schema.yml
|
||||
-g python
|
||||
-o satnogs-db-api-client
|
||||
-c satnogs-db-api-client/openapi-generator-config.json
|
||||
- >-
|
||||
docker-entrypoint.sh
|
||||
generate
|
||||
-i satnogs-db-api-client/api-schema.yml
|
||||
-g html2
|
||||
-o satnogs-db-api-client/html2
|
||||
-c satnogs-db-api-client/openapi-generator-config.json
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
paths:
|
||||
- satnogs-db-api-client
|
||||
|
||||
# 'static' stage
|
||||
static_js_css:
|
||||
stage: static
|
||||
image: node:11.13
|
||||
needs: []
|
||||
image: ${GITLAB_CI_IMAGE_NODE}
|
||||
script:
|
||||
- npm install -g eslint stylelint
|
||||
- eslint 'db/static/js/*.js'
|
||||
- stylelint 'db/static/css/*.css'
|
||||
static_python:
|
||||
- npm ci
|
||||
- node_modules/.bin/gulp
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
paths:
|
||||
- db/static/lib
|
||||
static:
|
||||
stage: static
|
||||
image: python:2.7
|
||||
needs: []
|
||||
image: ${GITLAB_CI_IMAGE_PYTHON}
|
||||
before_script:
|
||||
- pip install tox~=3.8.0
|
||||
- pip install "$GITLAB_CI_PYPI_TOX"
|
||||
script:
|
||||
- tox -e flake8
|
||||
- tox -e "flake8,isort,yapf,pylint"
|
||||
|
||||
# 'build' stage
|
||||
docs:
|
||||
stage: build
|
||||
needs: []
|
||||
image: ${GITLAB_CI_IMAGE_PYTHON}
|
||||
before_script:
|
||||
- pip install "$GITLAB_CI_PYPI_TOX"
|
||||
script:
|
||||
- rm -rf docs/_build
|
||||
- tox -e "docs"
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
paths:
|
||||
- docs/_build/html
|
||||
build:
|
||||
stage: build
|
||||
image: python:2.7
|
||||
needs:
|
||||
- job: static_js_css
|
||||
artifacts: true
|
||||
image: ${GITLAB_CI_IMAGE_PYTHON}
|
||||
before_script:
|
||||
- pip install "$GITLAB_CI_PYPI_TOX"
|
||||
script:
|
||||
- rm -rf dist
|
||||
- python setup.py sdist bdist_wheel
|
||||
- tox -e build
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
paths:
|
||||
- dist
|
||||
build3:
|
||||
build_api:
|
||||
stage: build
|
||||
image: python:3.6
|
||||
needs:
|
||||
- job: api
|
||||
artifacts: true
|
||||
image: ${GITLAB_CI_IMAGE_PYTHON}
|
||||
before_script:
|
||||
- pip install "$GITLAB_CI_PYPI_TOX"
|
||||
script:
|
||||
- cd satnogs-db-api-client
|
||||
- rm -rf dist
|
||||
- python setup.py sdist bdist_wheel
|
||||
- tox -e build
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
paths:
|
||||
- dist
|
||||
- satnogs-db-api-client/dist
|
||||
|
||||
# 'test' stage
|
||||
test:
|
||||
stage: test
|
||||
image: python:2.7
|
||||
needs:
|
||||
- job: static_js_css
|
||||
artifacts: true
|
||||
image: ${GITLAB_CI_IMAGE_PYTHON}
|
||||
before_script:
|
||||
- pip install tox~=3.8.0
|
||||
- pip install "$GITLAB_CI_PYPI_TOX"
|
||||
script:
|
||||
- tox -e deps,pytest
|
||||
|
||||
# 'deploy' stage
|
||||
docker:
|
||||
stage: deploy
|
||||
image: docker:18.09
|
||||
image: ${GITLAB_CI_IMAGE_DOCKER}
|
||||
services:
|
||||
- docker:18.09-dind
|
||||
- ${GITLAB_CI_IMAGE_DOCKER}-dind
|
||||
before_script:
|
||||
- apk --update add py-pip
|
||||
- pip install docker-compose~=1.23.0
|
||||
- pip install "$GITLAB_CI_PYPI_DOCKER_COMPOSE"
|
||||
script:
|
||||
- |
|
||||
[ -z "$CI_REGISTRY_IMAGE" ] || {
|
||||
CACHE_IMAGE="$CI_REGISTRY_IMAGE/satnogs-db:$CI_COMMIT_REF_NAME}"
|
||||
CACHE_IMAGE="$CI_REGISTRY_IMAGE/satnogs-db:$CI_COMMIT_REF_NAME"
|
||||
[ -z "$CI_COMMIT_TAG" ] || CACHE_IMAGE="$CI_REGISTRY_IMAGE/satnogs-db:latest"
|
||||
export CACHE_IMAGE
|
||||
}
|
||||
|
@ -87,12 +188,76 @@ docker:
|
|||
refs:
|
||||
- master
|
||||
- tags
|
||||
deploy:
|
||||
stage: deploy
|
||||
image: ${GITLAB_CI_IMAGE_PYTHON}
|
||||
before_script:
|
||||
- pip install "$GITLAB_CI_PYPI_TOX"
|
||||
script:
|
||||
- rm -rf dist
|
||||
- tox -e "upload"
|
||||
only:
|
||||
refs:
|
||||
- tags
|
||||
variables:
|
||||
- $PYPI_USERNAME
|
||||
- $PYPI_PASSWORD
|
||||
except:
|
||||
- triggers
|
||||
deploy_api:
|
||||
stage: deploy
|
||||
image: ${GITLAB_CI_IMAGE_PYTHON}
|
||||
before_script:
|
||||
- pip install "$GITLAB_CI_PYPI_TOX"
|
||||
script:
|
||||
- cd satnogs-db-api-client
|
||||
- rm -rf dist
|
||||
- tox -e "upload"
|
||||
only:
|
||||
refs:
|
||||
- tags
|
||||
variables:
|
||||
- $PYPI_USERNAME
|
||||
- $PYPI_PASSWORD
|
||||
except:
|
||||
- triggers
|
||||
pages:
|
||||
stage: deploy
|
||||
image: ${GITLAB_CI_IMAGE_ALPINE}
|
||||
script:
|
||||
- mv docs/_build/html/ public/
|
||||
- mv satnogs-db-api-client/html2/ public/api/
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
only:
|
||||
- tags
|
||||
|
||||
# 'sentry_release' stage
|
||||
sentry_release:
|
||||
stage: sentry_release
|
||||
image: ${GITLAB_CI_IMAGE_SENTRY_CLI}
|
||||
script:
|
||||
- sentry-cli releases new --finalize -p ${CI_PROJECT_NAME} ${CI_PROJECT_NAME}@${CI_COMMIT_TAG}
|
||||
- sentry-cli releases set-commits --auto ${CI_PROJECT_NAME}@${CI_COMMIT_TAG}
|
||||
only:
|
||||
refs:
|
||||
- tags
|
||||
variables:
|
||||
- $SENTRY_AUTH_TOKEN
|
||||
- $SENTRY_ORG
|
||||
|
||||
# 'trigger' stage
|
||||
trigger_master:
|
||||
stage: trigger
|
||||
image: alpine:3.9
|
||||
needs:
|
||||
- job: docker
|
||||
artifacts: false
|
||||
image: ${GITLAB_CI_IMAGE_ALPINE}
|
||||
before_script:
|
||||
- apk add --no-cache curl
|
||||
script:
|
||||
- PIPELINE_TRIGGERS_MASTER=$(echo "$PIPELINE_TRIGGERS_MASTER" | sed 's/{{CI_COMMIT_SHORT_SHA}}/'"$CI_COMMIT_SHORT_SHA"'/g')
|
||||
- for trigger in $PIPELINE_TRIGGERS_MASTER; do curl -X POST "$trigger"; done
|
||||
only:
|
||||
refs:
|
||||
|
@ -101,13 +266,66 @@ trigger_master:
|
|||
- $PIPELINE_TRIGGERS_MASTER
|
||||
trigger_latest:
|
||||
stage: trigger
|
||||
image: alpine:3.9
|
||||
needs:
|
||||
- job: docker
|
||||
artifacts: false
|
||||
image: ${GITLAB_CI_IMAGE_ALPINE}
|
||||
before_script:
|
||||
- apk add --no-cache curl
|
||||
script:
|
||||
- PIPELINE_TRIGGERS_LATEST=$(echo "$PIPELINE_TRIGGERS_LATEST" | sed 's/{{CI_COMMIT_TAG}}/'"$CI_COMMIT_TAG"'/g')
|
||||
- for trigger in $PIPELINE_TRIGGERS_LATEST; do curl -X POST "$trigger"; done
|
||||
only:
|
||||
refs:
|
||||
- tags
|
||||
variables:
|
||||
- $PIPELINE_TRIGGERS_LATEST
|
||||
|
||||
# 'security' stage
|
||||
include:
|
||||
- template: Security/Container-Scanning.gitlab-ci.yml
|
||||
- template: Security/Dependency-Scanning.gitlab-ci.yml
|
||||
- template: Security/SAST.gitlab-ci.yml
|
||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||
- template: Security/License-Scanning.gitlab-ci.yml
|
||||
container_scanning:
|
||||
stage: security
|
||||
needs:
|
||||
- job: docker
|
||||
artifacts: false
|
||||
variables:
|
||||
CI_APPLICATION_REPOSITORY: ${CI_REGISTRY_IMAGE}/satnogs-db
|
||||
CI_APPLICATION_TAG: ${CI_COMMIT_REF_NAME}
|
||||
rules:
|
||||
- if: $CI_REGISTRY_IMAGE && $CI_COMMIT_BRANCH == "master"
|
||||
- if: $CI_REGISTRY_IMAGE && $CI_COMMIT_TAG
|
||||
dependency_scanning:
|
||||
stage: security
|
||||
needs:
|
||||
- job: api
|
||||
artifacts: true
|
||||
variables:
|
||||
DS_DEFAULT_ANALYZERS: 'gemnasium,gemnasium-python,retire.js'
|
||||
gemnasium-python-dependency_scanning:
|
||||
before_script:
|
||||
- apt-get -q update
|
||||
- apt-get -qy install libmariadb-dev python3-pil libjpeg-dev
|
||||
sast:
|
||||
stage: security
|
||||
needs:
|
||||
- job: api
|
||||
artifacts: true
|
||||
variables:
|
||||
SAST_DISABLE_BABEL: 'true'
|
||||
secret_detection:
|
||||
stage: security
|
||||
needs:
|
||||
- job: api
|
||||
artifacts: true
|
||||
license_scanning:
|
||||
stage: security
|
||||
needs:
|
||||
- job: api
|
||||
artifacts: true
|
||||
- job: static_js_css
|
||||
artifacts: true
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
[MASTER]
|
||||
ignore=_version.py,migrations
|
||||
load-plugins=pylint_django
|
||||
ignored-argument-names=args|kwargs
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=
|
||||
C0412,
|
||||
R0801, # needs to remain disabled see https://github.com/PyCQA/pylint/issues/214
|
||||
|
||||
[TYPECHECK]
|
||||
# zmq.{EAGAIN,RCVTIMEO,XPUB} is dynamically generated and so pylint
|
||||
# doesn't see it, causing false positives.
|
||||
generated-members=
|
||||
zmq.EAGAIN,
|
||||
zmq.RCVTIMEO,
|
||||
zmq.XPUB
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
version: '2'
|
||||
formats:
|
||||
- 'epub'
|
||||
- 'pdf'
|
||||
python:
|
||||
version: '3.8'
|
||||
install:
|
||||
- path: '.'
|
||||
- requirements: 'docs/requirements.txt'
|
||||
build:
|
||||
image: 'latest'
|
|
@ -0,0 +1,8 @@
|
|||
node_modules
|
||||
db/_version.py
|
||||
db/*/migrations
|
||||
satnogs-db-api-client
|
||||
.tox
|
||||
build
|
||||
docs
|
||||
versioneer.py
|
|
@ -1,5 +1,5 @@
|
|||
FROM python:2
|
||||
MAINTAINER SatNOGS project <dev@satnogs.org>
|
||||
FROM python:3.9.12
|
||||
LABEL maintainer="SatNOGS project <dev@satnogs.org>"
|
||||
|
||||
WORKDIR /workdir/
|
||||
|
||||
|
@ -8,6 +8,7 @@ RUN groupadd -r satnogs \
|
|||
&& install -d -m 755 -o satnogs -g satnogs /var/run/celery
|
||||
|
||||
COPY requirements.txt /usr/local/src/satnogs-db/
|
||||
ARG SATNOGS_DECODERS_VERSION
|
||||
RUN pip install \
|
||||
--no-cache-dir \
|
||||
--no-deps \
|
||||
|
|
7
LICENSE
7
LICENSE
|
@ -1,7 +1,7 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
@ -643,7 +643,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
|
@ -658,5 +658,4 @@ specific requirements.
|
|||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# SatNOGS DB
|
||||
|
||||
SatNOGS DB is a transmitter suggestions and crowd-sourcing app.
|
||||
|
||||
## Contribute
|
||||
|
||||
Check out the [documentation](https://docs.satnogs.org/projects/satnogs-db/en/stable/) on how to setup a local development instance.
|
||||
|
||||
The main repository lives on [Gitlab](https://gitlab.com/librespacefoundation/satnogs/satnogs-db).
|
||||
|
||||
## Join
|
||||
|
||||
[![matrix](https://img.shields.io/badge/Matrix-%23satnogs:matrix.org-blue.svg)](https://riot.im/app/#/room/#satnogs:matrix.org)
|
||||
[![irc](https://img.shields.io/badge/IRC-%23satnogs%20on%20freenode-blue.svg)](https://webchat.freenode.net/?channels=satnogs)
|
||||
[![forum](https://img.shields.io/badge/forum-discourse-blue.svg)](https://community.libre.space/c/satnogs)
|
||||
|
||||
## Current Development
|
||||
|
||||
[![kanban](https://img.shields.io/badge/kanban-board-lightgray.svg)](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/boards/345706)
|
||||
[![build](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/badges/master/build.svg)](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/commits/master)
|
||||
[![coverage](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/badges/master/coverage.svg)](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/commits/master)
|
||||
|
||||
## License
|
||||
|
||||
[![license](https://img.shields.io/badge/license-AGPL%203.0-6672D8.svg)](LICENSE)
|
||||
[![Libre Space Foundation](https://img.shields.io/badge/%C2%A9%202014--2019-Libre%20Space%20Foundation-6672D8.svg)](https://librespacefoundation.org/)
|
115
README.md
115
README.md
|
@ -1,26 +1,109 @@
|
|||
# SatNOGS DB
|
||||
# SatNOGS DB Fork
|
||||
This lesser fork is for exploring the SatNOGS DB.
|
||||
|
||||
SatNOGS DB is a transmitter suggestions and crowd-sourcing app.
|
||||
* https://spacecruft.org/spacecruft/satnogs-db
|
||||
|
||||
## Contribute
|
||||
|
||||
Check out the [documentation](https://docs.satnogs.org/en/latest/satnogs-db/docs/) on how to setup a local development instance.
|
||||
# Install Dependencies
|
||||
Using Debian Stable (Bullseye/11).
|
||||
|
||||
The main repository lives on [Gitlab](https://gitlab.com/librespacefoundation/satnogs/satnogs-db).
|
||||
```
|
||||
sudo apt install git libmariadb-dev mariadb-server npm python3-pip sqlite3 virtualenvwrapper
|
||||
```
|
||||
|
||||
## Join
|
||||
# Repo setup
|
||||
I setup my `git` repo thusly.
|
||||
|
||||
[![matrix](https://img.shields.io/badge/Matrix-%23satnogs:matrix.org-blue.svg)](https://riot.im/app/#/room/#satnogs:matrix.org)
|
||||
[![irc](https://img.shields.io/badge/IRC-%23satnogs%20on%20freenode-blue.svg)](https://webchat.freenode.net/?channels=satnogs)
|
||||
[![forum](https://img.shields.io/badge/forum-discourse-blue.svg)](https://community.libre.space/c/satnogs)
|
||||
```
|
||||
git clone git@spacecruft.org:spacecruft/satnogs-db.git
|
||||
cd satnogs-db
|
||||
git remote add upstream https://gitlab.com/librespacefoundation/satnogs/satnogs-db.git
|
||||
git fetch upstream
|
||||
git checkout remotes/upstream/master
|
||||
git branch spacecruft
|
||||
git checkout spacecruft
|
||||
git push --set-upstream origin spacecruft
|
||||
# set to default branch in gitea
|
||||
```
|
||||
|
||||
## Current Development
|
||||
|
||||
[![kanban](https://img.shields.io/badge/kanban-board-lightgray.svg)](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/boards/345706)
|
||||
[![build](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/badges/dev/build.svg)](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/commits/dev)
|
||||
[![coverage](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/badges/dev/coverage.svg)](https://gitlab.com/librespacefoundation/satnogs/satnogs-db/commits/dev)
|
||||
# Setup
|
||||
Set up the environment.
|
||||
|
||||
## License
|
||||
To use `virtualenvwrapper`, you need to add it to the PATH.
|
||||
The easiest way is to just add this like to the end of
|
||||
`~/.bashrc`.
|
||||
|
||||
```
|
||||
source /usr/share/virtualenvwrapper/virtualenvwrapper.sh
|
||||
```
|
||||
|
||||
Then log out and back in or just re-source the file.
|
||||
This will enable the `mkvirtualenv` and `workon` commands.
|
||||
|
||||
|
||||
```
|
||||
cd satnogs-db
|
||||
mkvirtualenv satnogs-db -a .
|
||||
pip install --upgrade pip
|
||||
cp env-dist .env
|
||||
```
|
||||
|
||||
Then edit the `.env` file to add `ALLOWED_HOSTS` for remote acccess
|
||||
on LAN. Use IP address of *server*.
|
||||
|
||||
```
|
||||
ALLOWED_HOSTS='192.168.1.100'
|
||||
```
|
||||
|
||||
|
||||
# Front End Dependencies
|
||||
Install front end.
|
||||
Each command takes one minute or so to run.
|
||||
|
||||
```
|
||||
npm install
|
||||
./node_modules/.bin/gulp
|
||||
```
|
||||
|
||||
|
||||
# Populate Database
|
||||
Run thusly:
|
||||
|
||||
```
|
||||
workon satnogs-db # if you aren't already in the environment
|
||||
./bin/djangoctl.sh develop .
|
||||
```
|
||||
|
||||
Set up database in another terminal, when above command is ready
|
||||
and listening on port 8000:
|
||||
|
||||
```
|
||||
cd satnogs-db
|
||||
workon satnogs-db
|
||||
./bin/djangoctl.sh initialize
|
||||
```
|
||||
|
||||
# Access
|
||||
May need to open firewall on server, `TCP/8000`.
|
||||
|
||||
|
||||
# Use
|
||||
In web browser go to server IP, port 8000.
|
||||
|
||||
http://192.168.1.1:8000/
|
||||
|
||||
Log in with super user created above when initializing.
|
||||
It will verify email address. Look in the output of the
|
||||
terminal running the django command above for the URL
|
||||
to validate the email address (assuming no email is
|
||||
actually used).
|
||||
|
||||
|
||||
# Upstream
|
||||
See upstream `README-upstream.md`.
|
||||
|
||||
* https://db.satnogs.org/
|
||||
* https://gitlab.com/librespacefoundation/satnogs/satnogs-db.git
|
||||
* https://docs.satnogs.org/projects/satnogs-db/en/stable/
|
||||
|
||||
[![license](https://img.shields.io/badge/license-AGPL%203.0-6672D8.svg)](LICENSE)
|
||||
[![Libre Space Foundation](https://img.shields.io/badge/%C2%A9%202014--2017-Libre%20Space%20Foundation-6672D8.svg)](https://librespacefoundation.org/)
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class Auth0LoginConfig(AppConfig):
|
||||
name = 'auth0login'
|
|
@ -1,35 +0,0 @@
|
|||
import requests
|
||||
from social_core.backends.oauth import BaseOAuth2
|
||||
|
||||
|
||||
class Auth0(BaseOAuth2):
|
||||
"""Auth0 OAuth authentication backend"""
|
||||
name = 'auth0'
|
||||
SCOPE_SEPARATOR = ' '
|
||||
ACCESS_TOKEN_METHOD = 'POST'
|
||||
EXTRA_DATA = [
|
||||
('email', 'email')
|
||||
]
|
||||
|
||||
def authorization_url(self):
|
||||
"""Return the authorization endpoint."""
|
||||
return "https://" + self.setting('DOMAIN') + "/authorize"
|
||||
|
||||
def access_token_url(self):
|
||||
"""Return the token endpoint."""
|
||||
return "https://" + self.setting('DOMAIN') + "/oauth/token"
|
||||
|
||||
def get_user_id(self, details, response):
|
||||
"""Return current user id."""
|
||||
return details['user_id']
|
||||
|
||||
def get_user_details(self, response):
|
||||
url = 'https://' + self.setting('DOMAIN') + '/userinfo'
|
||||
headers = {'authorization': 'Bearer ' + response['access_token']}
|
||||
resp = requests.get(url, headers=headers)
|
||||
userinfo = resp.json()
|
||||
|
||||
return {'username': userinfo['nickname'],
|
||||
'email': userinfo['email'],
|
||||
# 'first_name': userinfo['name'],
|
||||
'user_id': userinfo['sub']}
|
|
@ -1,6 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# from django.db import models
|
||||
|
||||
# Create your models here.
|
|
@ -1,6 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -1,8 +0,0 @@
|
|||
from django.conf.urls import url, include
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url('^$', views.index),
|
||||
url(r'^', include('django.contrib.auth.urls', namespace='auth')),
|
||||
url(r'^', include('social_django.urls', namespace='social')),
|
||||
]
|
|
@ -1,7 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
def index(request):
|
||||
return render(request, 'index.html')
|
|
@ -72,7 +72,7 @@ run() {
|
|||
run_celery() {
|
||||
case "$1" in
|
||||
worker|beat)
|
||||
exec celery -A "$DJANGO_APP" "$1" -l INFO --workdir "$CELERY_VAR_RUN"
|
||||
exec celery --workdir "$CELERY_VAR_RUN" -A "$DJANGO_APP" "$1" -l INFO
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
"""pytest configuration file"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def celery_config():
|
||||
return {
|
||||
'broker_url': 'memory://',
|
||||
'result_backend': 'rpc',
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
# Script to refresh requirements.txt file
|
||||
#
|
||||
# Copyright (C) 2019 Libre Space Foundation <https://libre.space/>
|
||||
# Copyright (C) 2019-2022 Libre Space Foundation <https://libre.space/>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,12 +17,36 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
EXCLUDE_REGEXP="^\(pkg-resources\|satnogs-db\)"
|
||||
EXCLUDE_REGEXP="^\\(pkg[-_]resources\\|satnogs-db\\)"
|
||||
COMPATIBLE_REGEXP="^\\(satnogs-decoders\\)"
|
||||
VIRTUALENV_DIR=$(mktemp -d)
|
||||
PIP_COMMAND="$VIRTUALENV_DIR/bin/pip"
|
||||
PYTHON_VERSION="3.9"
|
||||
REQUIREMENTS="
|
||||
comm
|
||||
grep
|
||||
sed
|
||||
sort
|
||||
virtualenv
|
||||
"
|
||||
|
||||
# Check for required utilities
|
||||
for req in $REQUIREMENTS; do
|
||||
if ! which "$req" >/dev/null; then
|
||||
if [ -z "$has_missing" ]; then
|
||||
echo "$(basename "$0"): Missing script requirements!" 1>&2
|
||||
echo "Please install:" 1>&2
|
||||
has_missing=1
|
||||
fi
|
||||
echo " - '$req'" 1>&2
|
||||
fi
|
||||
done
|
||||
if [ -n "$has_missing" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create virtualenv
|
||||
virtualenv "$VIRTUALENV_DIR"
|
||||
virtualenv -p python$PYTHON_VERSION "$VIRTUALENV_DIR"
|
||||
|
||||
# Install package with dependencies
|
||||
"$PIP_COMMAND" install --no-cache-dir --force-reinstall .
|
||||
|
@ -55,6 +79,12 @@ _tmp_requirements_dev=$(mktemp)
|
|||
sort < requirements.txt | comm -13 - "$_tmp_requirements_dev" >> requirements-dev.txt
|
||||
rm -f "$_tmp_requirements_dev"
|
||||
|
||||
# Set compatible release packages
|
||||
if [ -n "$COMPATIBLE_REGEXP" ]; then
|
||||
sed -i 's/'"$COMPATIBLE_REGEXP"'==\([0-9]\+\)\(\.[0-9]\+\)\+$/\1~=\2.0/' requirements.txt
|
||||
sed -i 's/'"$COMPATIBLE_REGEXP"'==\([0-9]\+\)\(\.[0-9]\+\)\+$/\1~=\2.0/' requirements-dev.txt
|
||||
fi
|
||||
|
||||
# Verify dependency compatibility
|
||||
"$PIP_COMMAND" check
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from .celery import app as celery_app # noqa
|
||||
|
||||
"""The core django app for SatNOGS DB"""
|
||||
from ._version import get_versions
|
||||
from .celery import APP as celery_app # noqa
|
||||
|
||||
__all__ = ['celery_app']
|
||||
|
||||
from ._version import get_versions
|
||||
__version__ = get_versions()['version']
|
||||
del get_versions
|
||||
|
|
266
db/_version.py
266
db/_version.py
|
@ -6,7 +6,7 @@
|
|||
# that just contains the computed version number.
|
||||
|
||||
# This file is released into the public domain. Generated by
|
||||
# versioneer-0.18 (https://github.com/warner/python-versioneer)
|
||||
# versioneer-0.22 (https://github.com/python-versioneer/python-versioneer)
|
||||
|
||||
"""Git implementation of _version.py."""
|
||||
|
||||
|
@ -15,6 +15,8 @@ import os
|
|||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Callable, Dict
|
||||
import functools
|
||||
|
||||
|
||||
def get_keywords():
|
||||
|
@ -52,12 +54,12 @@ class NotThisMethod(Exception):
|
|||
"""Exception raised if a method is not valid for the current scenario."""
|
||||
|
||||
|
||||
LONG_VERSION_PY = {}
|
||||
HANDLERS = {}
|
||||
LONG_VERSION_PY: Dict[str, str] = {}
|
||||
HANDLERS: Dict[str, Dict[str, Callable]] = {}
|
||||
|
||||
|
||||
def register_vcs_handler(vcs, method): # decorator
|
||||
"""Decorator to mark a method as the handler for a particular VCS."""
|
||||
"""Create decorator to mark a method as the handler of a VCS."""
|
||||
def decorate(f):
|
||||
"""Store f in HANDLERS[vcs][method]."""
|
||||
if vcs not in HANDLERS:
|
||||
|
@ -71,17 +73,25 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
|
|||
env=None):
|
||||
"""Call the given command(s)."""
|
||||
assert isinstance(commands, list)
|
||||
p = None
|
||||
for c in commands:
|
||||
process = None
|
||||
|
||||
popen_kwargs = {}
|
||||
if sys.platform == "win32":
|
||||
# This hides the console window if pythonw.exe is used
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
popen_kwargs["startupinfo"] = startupinfo
|
||||
|
||||
for command in commands:
|
||||
try:
|
||||
dispcmd = str([c] + args)
|
||||
dispcmd = str([command] + args)
|
||||
# remember shell=False, so use git.cmd on windows, not just git
|
||||
p = subprocess.Popen([c] + args, cwd=cwd, env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=(subprocess.PIPE if hide_stderr
|
||||
else None))
|
||||
process = subprocess.Popen([command] + args, cwd=cwd, env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=(subprocess.PIPE if hide_stderr
|
||||
else None), **popen_kwargs)
|
||||
break
|
||||
except EnvironmentError:
|
||||
except OSError:
|
||||
e = sys.exc_info()[1]
|
||||
if e.errno == errno.ENOENT:
|
||||
continue
|
||||
|
@ -93,15 +103,13 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
|
|||
if verbose:
|
||||
print("unable to find command, tried %s" % (commands,))
|
||||
return None, None
|
||||
stdout = p.communicate()[0].strip()
|
||||
if sys.version_info[0] >= 3:
|
||||
stdout = stdout.decode()
|
||||
if p.returncode != 0:
|
||||
stdout = process.communicate()[0].strip().decode()
|
||||
if process.returncode != 0:
|
||||
if verbose:
|
||||
print("unable to run %s (error)" % dispcmd)
|
||||
print("stdout was %s" % stdout)
|
||||
return None, p.returncode
|
||||
return stdout, p.returncode
|
||||
return None, process.returncode
|
||||
return stdout, process.returncode
|
||||
|
||||
|
||||
def versions_from_parentdir(parentdir_prefix, root, verbose):
|
||||
|
@ -113,15 +121,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
|
|||
"""
|
||||
rootdirs = []
|
||||
|
||||
for i in range(3):
|
||||
for _ in range(3):
|
||||
dirname = os.path.basename(root)
|
||||
if dirname.startswith(parentdir_prefix):
|
||||
return {"version": dirname[len(parentdir_prefix):],
|
||||
"full-revisionid": None,
|
||||
"dirty": False, "error": None, "date": None}
|
||||
else:
|
||||
rootdirs.append(root)
|
||||
root = os.path.dirname(root) # up a level
|
||||
rootdirs.append(root)
|
||||
root = os.path.dirname(root) # up a level
|
||||
|
||||
if verbose:
|
||||
print("Tried directories %s but none started with prefix %s" %
|
||||
|
@ -138,22 +145,21 @@ def git_get_keywords(versionfile_abs):
|
|||
# _version.py.
|
||||
keywords = {}
|
||||
try:
|
||||
f = open(versionfile_abs, "r")
|
||||
for line in f.readlines():
|
||||
if line.strip().startswith("git_refnames ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
keywords["refnames"] = mo.group(1)
|
||||
if line.strip().startswith("git_full ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
keywords["full"] = mo.group(1)
|
||||
if line.strip().startswith("git_date ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
keywords["date"] = mo.group(1)
|
||||
f.close()
|
||||
except EnvironmentError:
|
||||
with open(versionfile_abs, "r") as fobj:
|
||||
for line in fobj:
|
||||
if line.strip().startswith("git_refnames ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
keywords["refnames"] = mo.group(1)
|
||||
if line.strip().startswith("git_full ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
keywords["full"] = mo.group(1)
|
||||
if line.strip().startswith("git_date ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
keywords["date"] = mo.group(1)
|
||||
except OSError:
|
||||
pass
|
||||
return keywords
|
||||
|
||||
|
@ -161,10 +167,14 @@ def git_get_keywords(versionfile_abs):
|
|||
@register_vcs_handler("git", "keywords")
|
||||
def git_versions_from_keywords(keywords, tag_prefix, verbose):
|
||||
"""Get version information from git keywords."""
|
||||
if not keywords:
|
||||
raise NotThisMethod("no keywords at all, weird")
|
||||
if "refnames" not in keywords:
|
||||
raise NotThisMethod("Short version file found")
|
||||
date = keywords.get("date")
|
||||
if date is not None:
|
||||
# Use only the last line. Previous lines may contain GPG signature
|
||||
# information.
|
||||
date = date.splitlines()[-1]
|
||||
|
||||
# git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
|
||||
# datestamp. However we prefer "%ci" (which expands to an "ISO-8601
|
||||
# -like" string, which we must then edit to make compliant), because
|
||||
|
@ -177,11 +187,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
|
|||
if verbose:
|
||||
print("keywords are unexpanded, not using")
|
||||
raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
|
||||
refs = set([r.strip() for r in refnames.strip("()").split(",")])
|
||||
refs = {r.strip() for r in refnames.strip("()").split(",")}
|
||||
# starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
|
||||
# just "foo-1.0". If we see a "tag: " prefix, prefer those.
|
||||
TAG = "tag: "
|
||||
tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
|
||||
tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
|
||||
if not tags:
|
||||
# Either we're using git < 1.8.3, or there really are no tags. We use
|
||||
# a heuristic: assume all version tags have a digit. The old git %d
|
||||
|
@ -190,7 +200,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
|
|||
# between branches and tags. By ignoring refnames without digits, we
|
||||
# filter out many common branch names like "release" and
|
||||
# "stabilization", as well as "HEAD" and "master".
|
||||
tags = set([r for r in refs if re.search(r'\d', r)])
|
||||
tags = {r for r in refs if re.search(r'\d', r)}
|
||||
if verbose:
|
||||
print("discarding '%s', no digits" % ",".join(refs - tags))
|
||||
if verbose:
|
||||
|
@ -199,6 +209,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
|
|||
# sorting will prefer e.g. "2.0" over "2.0rc1"
|
||||
if ref.startswith(tag_prefix):
|
||||
r = ref[len(tag_prefix):]
|
||||
# Filter out refs that exactly match prefix or that don't start
|
||||
# with a number once the prefix is stripped (mostly a concern
|
||||
# when prefix is '')
|
||||
if not re.match(r'\d', r):
|
||||
continue
|
||||
if verbose:
|
||||
print("picking %s" % r)
|
||||
return {"version": r,
|
||||
|
@ -214,7 +229,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
|
|||
|
||||
|
||||
@register_vcs_handler("git", "pieces_from_vcs")
|
||||
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
|
||||
def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
|
||||
"""Get version from 'git describe' in the root of the source tree.
|
||||
|
||||
This only gets called if the git-archive 'subst' keywords were *not*
|
||||
|
@ -225,24 +240,32 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
|
|||
if sys.platform == "win32":
|
||||
GITS = ["git.cmd", "git.exe"]
|
||||
|
||||
out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
|
||||
hide_stderr=True)
|
||||
# GIT_DIR can interfere with correct operation of Versioneer.
|
||||
# It may be intended to be passed to the Versioneer-versioned project,
|
||||
# but that should not change where we get our version from.
|
||||
env = os.environ.copy()
|
||||
env.pop("GIT_DIR", None)
|
||||
runner = functools.partial(runner, env=env)
|
||||
|
||||
_, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
|
||||
hide_stderr=True)
|
||||
if rc != 0:
|
||||
if verbose:
|
||||
print("Directory %s not under git control" % root)
|
||||
raise NotThisMethod("'git rev-parse --git-dir' returned error")
|
||||
|
||||
MATCH_ARGS = ["--match", "%s*" % tag_prefix] if tag_prefix else []
|
||||
|
||||
# if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
|
||||
# if there isn't one, this yields HEX[-dirty] (no NUM)
|
||||
describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
|
||||
"--always", "--long",
|
||||
"--match", "%s*" % tag_prefix],
|
||||
cwd=root)
|
||||
describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty",
|
||||
"--always", "--long", *MATCH_ARGS],
|
||||
cwd=root)
|
||||
# --long was added in git-1.5.5
|
||||
if describe_out is None:
|
||||
raise NotThisMethod("'git describe' failed")
|
||||
describe_out = describe_out.strip()
|
||||
full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
|
||||
full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
|
||||
if full_out is None:
|
||||
raise NotThisMethod("'git rev-parse' failed")
|
||||
full_out = full_out.strip()
|
||||
|
@ -252,6 +275,39 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
|
|||
pieces["short"] = full_out[:7] # maybe improved later
|
||||
pieces["error"] = None
|
||||
|
||||
branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
|
||||
cwd=root)
|
||||
# --abbrev-ref was added in git-1.6.3
|
||||
if rc != 0 or branch_name is None:
|
||||
raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
|
||||
branch_name = branch_name.strip()
|
||||
|
||||
if branch_name == "HEAD":
|
||||
# If we aren't exactly on a branch, pick a branch which represents
|
||||
# the current commit. If all else fails, we are on a branchless
|
||||
# commit.
|
||||
branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
|
||||
# --contains was added in git-1.5.4
|
||||
if rc != 0 or branches is None:
|
||||
raise NotThisMethod("'git branch --contains' returned error")
|
||||
branches = branches.split("\n")
|
||||
|
||||
# Remove the first line if we're running detached
|
||||
if "(" in branches[0]:
|
||||
branches.pop(0)
|
||||
|
||||
# Strip off the leading "* " from the list of branches.
|
||||
branches = [branch[2:] for branch in branches]
|
||||
if "master" in branches:
|
||||
branch_name = "master"
|
||||
elif not branches:
|
||||
branch_name = None
|
||||
else:
|
||||
# Pick the first branch that is returned. Good or bad.
|
||||
branch_name = branches[0]
|
||||
|
||||
pieces["branch"] = branch_name
|
||||
|
||||
# parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
|
||||
# TAG might have hyphens.
|
||||
git_describe = describe_out
|
||||
|
@ -268,7 +324,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
|
|||
# TAG-NUM-gHEX
|
||||
mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
|
||||
if not mo:
|
||||
# unparseable. Maybe git-describe is misbehaving?
|
||||
# unparsable. Maybe git-describe is misbehaving?
|
||||
pieces["error"] = ("unable to parse git-describe output: '%s'"
|
||||
% describe_out)
|
||||
return pieces
|
||||
|
@ -293,13 +349,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
|
|||
else:
|
||||
# HEX: no tags
|
||||
pieces["closest-tag"] = None
|
||||
count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
|
||||
cwd=root)
|
||||
count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
|
||||
pieces["distance"] = int(count_out) # total number of commits
|
||||
|
||||
# commit date: see ISO-8601 comment in git_versions_from_keywords()
|
||||
date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
|
||||
cwd=root)[0].strip()
|
||||
date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
|
||||
# Use only the last line. Previous lines may contain GPG signature
|
||||
# information.
|
||||
date = date.splitlines()[-1]
|
||||
pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
|
||||
|
||||
return pieces
|
||||
|
@ -337,19 +394,67 @@ def render_pep440(pieces):
|
|||
return rendered
|
||||
|
||||
|
||||
def render_pep440_pre(pieces):
|
||||
"""TAG[.post.devDISTANCE] -- No -dirty.
|
||||
def render_pep440_branch(pieces):
|
||||
"""TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
|
||||
|
||||
The ".dev0" means not master branch. Note that .dev0 sorts backwards
|
||||
(a feature branch will appear "older" than the master branch).
|
||||
|
||||
Exceptions:
|
||||
1: no tags. 0.post.devDISTANCE
|
||||
1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
rendered = pieces["closest-tag"]
|
||||
if pieces["distance"]:
|
||||
rendered += ".post.dev%d" % pieces["distance"]
|
||||
if pieces["distance"] or pieces["dirty"]:
|
||||
if pieces["branch"] != "master":
|
||||
rendered += ".dev0"
|
||||
rendered += plus_or_dot(pieces)
|
||||
rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dirty"
|
||||
else:
|
||||
# exception #1
|
||||
rendered = "0.post.dev%d" % pieces["distance"]
|
||||
rendered = "0"
|
||||
if pieces["branch"] != "master":
|
||||
rendered += ".dev0"
|
||||
rendered += "+untagged.%d.g%s" % (pieces["distance"],
|
||||
pieces["short"])
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dirty"
|
||||
return rendered
|
||||
|
||||
|
||||
def pep440_split_post(ver):
|
||||
"""Split pep440 version string at the post-release segment.
|
||||
|
||||
Returns the release segments before the post-release and the
|
||||
post-release version number (or -1 if no post-release segment is present).
|
||||
"""
|
||||
vc = str.split(ver, ".post")
|
||||
return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
|
||||
|
||||
|
||||
def render_pep440_pre(pieces):
|
||||
"""TAG[.postN.devDISTANCE] -- No -dirty.
|
||||
|
||||
Exceptions:
|
||||
1: no tags. 0.post0.devDISTANCE
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
if pieces["distance"]:
|
||||
# update the post release segment
|
||||
tag_version, post_version = pep440_split_post(pieces["closest-tag"])
|
||||
rendered = tag_version
|
||||
if post_version is not None:
|
||||
rendered += ".post%d.dev%d" % (post_version+1, pieces["distance"])
|
||||
else:
|
||||
rendered += ".post0.dev%d" % (pieces["distance"])
|
||||
else:
|
||||
# no commits, use the tag as the version
|
||||
rendered = pieces["closest-tag"]
|
||||
else:
|
||||
# exception #1
|
||||
rendered = "0.post0.dev%d" % pieces["distance"]
|
||||
return rendered
|
||||
|
||||
|
||||
|
@ -380,12 +485,41 @@ def render_pep440_post(pieces):
|
|||
return rendered
|
||||
|
||||
|
||||
def render_pep440_post_branch(pieces):
|
||||
"""TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
|
||||
|
||||
The ".dev0" means not master branch.
|
||||
|
||||
Exceptions:
|
||||
1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
rendered = pieces["closest-tag"]
|
||||
if pieces["distance"] or pieces["dirty"]:
|
||||
rendered += ".post%d" % pieces["distance"]
|
||||
if pieces["branch"] != "master":
|
||||
rendered += ".dev0"
|
||||
rendered += plus_or_dot(pieces)
|
||||
rendered += "g%s" % pieces["short"]
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dirty"
|
||||
else:
|
||||
# exception #1
|
||||
rendered = "0.post%d" % pieces["distance"]
|
||||
if pieces["branch"] != "master":
|
||||
rendered += ".dev0"
|
||||
rendered += "+g%s" % pieces["short"]
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dirty"
|
||||
return rendered
|
||||
|
||||
|
||||
def render_pep440_old(pieces):
|
||||
"""TAG[.postDISTANCE[.dev0]] .
|
||||
|
||||
The ".dev0" means dirty.
|
||||
|
||||
Eexceptions:
|
||||
Exceptions:
|
||||
1: no tags. 0.postDISTANCE[.dev0]
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
|
@ -456,10 +590,14 @@ def render(pieces, style):
|
|||
|
||||
if style == "pep440":
|
||||
rendered = render_pep440(pieces)
|
||||
elif style == "pep440-branch":
|
||||
rendered = render_pep440_branch(pieces)
|
||||
elif style == "pep440-pre":
|
||||
rendered = render_pep440_pre(pieces)
|
||||
elif style == "pep440-post":
|
||||
rendered = render_pep440_post(pieces)
|
||||
elif style == "pep440-post-branch":
|
||||
rendered = render_pep440_post_branch(pieces)
|
||||
elif style == "pep440-old":
|
||||
rendered = render_pep440_old(pieces)
|
||||
elif style == "git-describe":
|
||||
|
@ -495,7 +633,7 @@ def get_versions():
|
|||
# versionfile_source is the relative path from the top of the source
|
||||
# tree (where the .git directory might live) to this file. Invert
|
||||
# this to find the root from __file__.
|
||||
for i in cfg.versionfile_source.split('/'):
|
||||
for _ in cfg.versionfile_source.split('/'):
|
||||
root = os.path.dirname(root)
|
||||
except NameError:
|
||||
return {"version": "0+unknown", "full-revisionid": None,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
"""SatNOGS DB API hooks for drf-spectcular"""
|
||||
|
||||
EXCLUDED_PATHS = [
|
||||
'/api/satellites/{satellite_entry__norad_cat_id}/',
|
||||
]
|
||||
|
||||
|
||||
def exclude_paths_hook(endpoints):
|
||||
""" Excluding paths that are defined in EXCLUDED_PATHS list """
|
||||
return [
|
||||
(path, path_regex, method, callback) for path, path_regex, method, callback in endpoints
|
||||
if path not in EXCLUDED_PATHS
|
||||
]
|
|
@ -1,31 +1,168 @@
|
|||
from django_filters.rest_framework import FilterSet
|
||||
from django_filters import rest_framework as filters
|
||||
"""SatNOGS DB django rest framework Filters class"""
|
||||
import django_filters
|
||||
from django_filters import Filter
|
||||
from django_filters import rest_framework as filters
|
||||
from django_filters.rest_framework import FilterSet
|
||||
|
||||
from db.base.models import Transmitter, DemodData, Satellite
|
||||
from db.base.models import SATELLITE_STATUS, Artifact, DemodData, LatestTleSet, Mode, Satellite, \
|
||||
Transmitter
|
||||
|
||||
|
||||
class ListFilter(Filter):
|
||||
"""Custom Filter to use list"""
|
||||
def filter(self, qs, value):
|
||||
"""Returns a QuerySet using list of values as input"""
|
||||
if value:
|
||||
value_list = value.replace(' ', '').split(u',')
|
||||
kwargs = {'{0}__in'.format(self.field_name): value_list}
|
||||
return qs.filter(**kwargs)
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class TransmitterViewFilter(FilterSet):
|
||||
"""SatNOGS DB Transmitter API View Filter"""
|
||||
alive = filters.BooleanFilter(field_name='status', label='Alive', method='filter_status')
|
||||
mode = django_filters.ModelChoiceFilter(
|
||||
field_name='downlink_mode', lookup_expr='exact', queryset=Mode.objects.all()
|
||||
)
|
||||
|
||||
# see https://django-filter.readthedocs.io/en/master/ref/filters.html for
|
||||
# W0613
|
||||
def filter_status(self, queryset, name, value): # pylint: disable=W0613,R0201
|
||||
"""Returns Transmitters that are either functional or non-functional"""
|
||||
if value:
|
||||
transmitters = queryset.filter(status='active')
|
||||
else:
|
||||
transmitters = queryset.exclude(status='active')
|
||||
return transmitters
|
||||
|
||||
satellite__norad_cat_id = filters.NumberFilter(
|
||||
field_name='satellite__satellite_entry__norad_cat_id', label='Satellite NORAD ID'
|
||||
)
|
||||
sat_id = django_filters.CharFilter(
|
||||
method='get_current_sat_transmitter_from_sat_id', label='Satellite ID'
|
||||
)
|
||||
|
||||
# pylint: disable=W0613,R0201
|
||||
def get_current_sat_transmitter_from_sat_id(self, queryset, field_name, value):
|
||||
"""Return the transmitter from the parent satellite in case a merged
|
||||
|
||||
satellite id is searched
|
||||
"""
|
||||
if value:
|
||||
id_list = value.replace(' ', '').split(u',')
|
||||
parent_id_list = []
|
||||
|
||||
qs = Satellite.objects.select_related('associated_satellite').filter(
|
||||
satellite_entry__approved=True, satellite_identifier__sat_id__in=id_list
|
||||
)
|
||||
|
||||
try:
|
||||
sats = qs.all()
|
||||
for sat in sats:
|
||||
if sat.associated_satellite is None:
|
||||
parent_id_list.append(sat.id)
|
||||
else:
|
||||
parent_id_list.append(sat.associated_satellite.id)
|
||||
except Satellite.DoesNotExist:
|
||||
return qs
|
||||
|
||||
return Transmitter.objects.select_related('satellite__associated_satellite').filter(
|
||||
satellite__satellite_entry__approved=True, satellite__id__in=parent_id_list
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
class Meta:
|
||||
model = Transmitter
|
||||
fields = ['mode', 'type', 'satellite__norad_cat_id']
|
||||
fields = [
|
||||
'uuid', 'mode', 'uplink_mode', 'type', 'satellite__norad_cat_id', 'alive', 'status',
|
||||
'service'
|
||||
]
|
||||
|
||||
|
||||
class SatelliteViewFilter(FilterSet):
|
||||
''' filter on decayed field '''
|
||||
in_orbit = filters.BooleanFilter(name='decayed',
|
||||
label='In orbit',
|
||||
lookup_expr='isnull')
|
||||
"""SatNOGS DB Satellite API View Filter
|
||||
|
||||
filter on decayed field
|
||||
"""
|
||||
in_orbit = filters.BooleanFilter(
|
||||
field_name='satellite_entry__decayed', label='In orbit', lookup_expr='isnull'
|
||||
)
|
||||
status = filters.ChoiceFilter(
|
||||
field_name='satellite_entry__status',
|
||||
label='Satellite Status',
|
||||
choices=list(zip(SATELLITE_STATUS, SATELLITE_STATUS))
|
||||
)
|
||||
norad_cat_id = filters.NumberFilter(
|
||||
field_name='satellite_entry__norad_cat_id', label='Satellite NORAD ID'
|
||||
)
|
||||
sat_id = django_filters.CharFilter(method='get_current_sat_from_sat_id', label='Satellite ID')
|
||||
|
||||
# pylint: disable=W0613,R0201
|
||||
def get_current_sat_from_sat_id(self, queryset, field_name, value):
|
||||
"""Return the parent Satellite in case a merged
|
||||
|
||||
satellite id is searched
|
||||
"""
|
||||
if value:
|
||||
qs = Satellite.objects.select_related('associated_satellite').filter(
|
||||
satellite_entry__approved=True, satellite_identifier__sat_id=value
|
||||
)
|
||||
try:
|
||||
sat = qs.get()
|
||||
if sat.associated_satellite is None:
|
||||
return qs
|
||||
|
||||
qs = Satellite.objects.filter(
|
||||
satellite_entry__approved=True, id=sat.associated_satellite.id
|
||||
)
|
||||
return qs
|
||||
except Satellite.DoesNotExist:
|
||||
return qs
|
||||
|
||||
return queryset
|
||||
|
||||
class Meta:
|
||||
model = Satellite
|
||||
fields = ['norad_cat_id', 'status']
|
||||
fields = ['norad_cat_id', 'status', 'in_orbit']
|
||||
|
||||
|
||||
class TelemetryViewFilter(FilterSet):
|
||||
satellite = django_filters.NumberFilter(name='satellite__norad_cat_id',
|
||||
lookup_expr='exact')
|
||||
"""SatNOGS DB Telemetry API View Filter"""
|
||||
satellite = django_filters.NumberFilter(
|
||||
field_name='satellite__satellite_entry__norad_cat_id',
|
||||
lookup_expr='exact',
|
||||
label='Satellite NORAD ID'
|
||||
)
|
||||
sat_id = ListFilter(field_name='satellite__satellite_identifier__sat_id', label='Satellite ID')
|
||||
start = django_filters.IsoDateTimeFilter(field_name='timestamp', lookup_expr='gte')
|
||||
end = django_filters.IsoDateTimeFilter(field_name='timestamp', lookup_expr='lte')
|
||||
|
||||
class Meta:
|
||||
model = DemodData
|
||||
fields = ['satellite']
|
||||
fields = ['satellite', 'app_source', 'observer', 'transmitter']
|
||||
|
||||
|
||||
class LatestTleSetViewFilter(FilterSet):
|
||||
"""SatNOGS DB LatestTleSet API View Filter"""
|
||||
norad_cat_id = django_filters.NumberFilter(
|
||||
field_name='satellite__satellite_entry__norad_cat_id',
|
||||
lookup_expr='exact',
|
||||
label='Satellite NORAD ID'
|
||||
)
|
||||
sat_id = ListFilter(field_name='satellite__satellite_identifier__sat_id', label='Satellite ID')
|
||||
|
||||
class Meta:
|
||||
model = LatestTleSet
|
||||
fields = ['norad_cat_id']
|
||||
|
||||
|
||||
class ArtifactViewFilter(FilterSet):
|
||||
"""SatNOGS DB Artifact API View Filter"""
|
||||
class Meta:
|
||||
model = Artifact
|
||||
fields = [
|
||||
'network_obs_id',
|
||||
]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
Custom pagination classes for REST framework
|
||||
"""
|
||||
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
@ -18,14 +17,13 @@ class LinkedHeaderPageNumberPagination(PageNumberPagination):
|
|||
next_url = self.get_next_link()
|
||||
previous_url = self.get_previous_link()
|
||||
|
||||
link = ''
|
||||
if next_url is not None and previous_url is not None:
|
||||
link = '<{next_url}>; rel="next", <{previous_url}>; rel="prev"'
|
||||
elif next_url is not None:
|
||||
link = '<{next_url}>; rel="next"'
|
||||
elif previous_url is not None:
|
||||
link = '<{previous_url}>; rel="prev"'
|
||||
else:
|
||||
link = ''
|
||||
link = link.format(next_url=next_url, previous_url=previous_url)
|
||||
headers = {'Link': link} if link else {}
|
||||
return Response(data, headers=headers)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
"""SatNOGS DB django rest framework API custom parsers"""
|
||||
from pyld import jsonld
|
||||
from rest_framework.parsers import JSONParser
|
||||
|
||||
from db.base.structured_data import get_structured_data
|
||||
|
||||
|
||||
class JSONLDParser(JSONParser): # pylint: disable=R0903
|
||||
""" Parser for JSONLD. """
|
||||
|
||||
media_type = 'application/ld+json'
|
||||
|
||||
def parse(self, stream, media_type=None, parser_context=None):
|
||||
""" Render `data` into JSONLD, returning a bytestring. """
|
||||
raw_data = super().parse(stream, media_type, parser_context)
|
||||
structured_data = get_structured_data(parser_context['view'].basename, [])
|
||||
data = jsonld.frame(raw_data, structured_data.frame, {'omitGraph': False})
|
||||
return data
|
|
@ -0,0 +1,23 @@
|
|||
"""SatNOGS DB API permissions, django rest framework"""
|
||||
from rest_framework import permissions
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
|
||||
class SafeMethodsWithPermission(permissions.BasePermission):
|
||||
"""Access non-destructive methods (like GET and HEAD) with API Key"""
|
||||
def has_permission(self, request, view):
|
||||
return self.has_object_permission(request, view)
|
||||
|
||||
def has_object_permission(self, request, view, obj=None):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return request.user.is_authenticated
|
||||
return True
|
||||
|
||||
|
||||
class IsAuthenticatedOrOptions(IsAuthenticated):
|
||||
"""Allow unauthenticated access for OPTIONS method,
|
||||
check authentication for all other methods."""
|
||||
def has_permission(self, request, view):
|
||||
if request.method == 'OPTIONS':
|
||||
return True
|
||||
return super().has_permission(request, view)
|
|
@ -0,0 +1,28 @@
|
|||
"""SatNOGS DB django rest framework API custom renderers"""
|
||||
from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer
|
||||
|
||||
from db.base.structured_data import get_structured_data
|
||||
|
||||
|
||||
class JSONLDRenderer(JSONRenderer):
|
||||
""" Renderer which serializes to JSONLD. """
|
||||
|
||||
media_type = 'application/ld+json'
|
||||
format = 'json-ld'
|
||||
|
||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||
""" Render `data` into JSONLD, returning a bytestring. """
|
||||
if renderer_context['response'].exception:
|
||||
return super().render(data, accepted_media_type, renderer_context)
|
||||
|
||||
structured_data = get_structured_data(renderer_context['view'].basename, data)
|
||||
jsonld = structured_data.get_jsonld()
|
||||
return super().render(jsonld, accepted_media_type, renderer_context)
|
||||
|
||||
|
||||
class BrowserableJSONLDRenderer(BrowsableAPIRenderer):
|
||||
""" Renderer for Browserable API with JSONLD format. """
|
||||
format = 'browse-json-ld'
|
||||
|
||||
def get_default_renderer(self, view):
|
||||
return JSONLDRenderer()
|
|
@ -1,75 +1,561 @@
|
|||
"""SatNOGS DB API serializers, django rest framework"""
|
||||
# pylint: disable=R0201
|
||||
|
||||
import h5py
|
||||
from django.utils.datastructures import MultiValueDictKeyError
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiExample, extend_schema_field, extend_schema_serializer
|
||||
from rest_framework import serializers
|
||||
|
||||
from db.base.models import Mode, Satellite, Transmitter, DemodData
|
||||
from db.base.models import TRANSMITTER_STATUS, Artifact, DemodData, LatestTleSet, Mode, \
|
||||
Satellite, SatelliteEntry, Telemetry, Transmitter, TransmitterEntry
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Mode Example 1',
|
||||
summary='Example: list all modes',
|
||||
description='This is a truncated example response for listing all RF Mode entries',
|
||||
value=[
|
||||
{
|
||||
'id': 49,
|
||||
'name': 'AFSK'
|
||||
},
|
||||
],
|
||||
response_only=True, # signal that example only applies to responses
|
||||
),
|
||||
]
|
||||
)
|
||||
class ModeSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB Mode API Serializer"""
|
||||
class Meta:
|
||||
model = Mode
|
||||
fields = ('id', 'name')
|
||||
|
||||
|
||||
class SatTelemetrySerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB satellite telemetry API Serializer"""
|
||||
class Meta:
|
||||
model = Telemetry
|
||||
fields = ['decoder']
|
||||
|
||||
|
||||
class SatelliteEntrySerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB SatelliteEntry API Serializer"""
|
||||
class Meta:
|
||||
model = SatelliteEntry
|
||||
fields = (
|
||||
'satellite_identifier', 'norad_cat_id', 'name', 'names', 'status', 'citation',
|
||||
'created_by'
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Satellite Example 1',
|
||||
summary='Example: retrieving ISS',
|
||||
description='This is an example response for retrieving the ISS entry, NORAD ID 25544',
|
||||
value={
|
||||
'norad_cat_id': 25544,
|
||||
'name': 'ISS',
|
||||
'names': 'ZARYA',
|
||||
'image': 'https://db-satnogs.freetls.fastly.net/media/satellites/ISS.jpg',
|
||||
'status': 'alive',
|
||||
'decayed': None,
|
||||
'launched': '1998-11-20T00:00:00Z',
|
||||
'deployed': '1998-11-20T00:00:00Z',
|
||||
'website': 'https://www.nasa.gov/mission_pages/station/main/index.html',
|
||||
'operator': 'None',
|
||||
'countries': 'RU,US',
|
||||
'telemetries': [{
|
||||
'decoder': 'iss'
|
||||
}]
|
||||
},
|
||||
response_only=True, # signal that example only applies to responses
|
||||
),
|
||||
]
|
||||
)
|
||||
class SatelliteSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB Satellite API Serializer"""
|
||||
|
||||
sat_id = serializers.SerializerMethodField()
|
||||
norad_cat_id = serializers.SerializerMethodField()
|
||||
norad_follow_id = serializers.SerializerMethodField()
|
||||
name = serializers.SerializerMethodField()
|
||||
names = serializers.SerializerMethodField()
|
||||
image = serializers.SerializerMethodField()
|
||||
status = serializers.SerializerMethodField()
|
||||
decayed = serializers.SerializerMethodField()
|
||||
launched = serializers.SerializerMethodField()
|
||||
deployed = serializers.SerializerMethodField()
|
||||
website = serializers.SerializerMethodField()
|
||||
operator = serializers.SerializerMethodField()
|
||||
countries = serializers.SerializerMethodField()
|
||||
telemetries = serializers.SerializerMethodField()
|
||||
updated = serializers.SerializerMethodField()
|
||||
citation = serializers.SerializerMethodField()
|
||||
associated_satellites = serializers.SerializerMethodField()
|
||||
operator = serializers.SerializerMethodField()
|
||||
is_frequency_violator = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Satellite
|
||||
fields = ('norad_cat_id', 'name', 'names', 'image', 'status', 'decayed')
|
||||
fields = (
|
||||
'sat_id', 'norad_cat_id', 'norad_follow_id', 'name', 'names', 'image', 'status',
|
||||
'decayed', 'launched', 'deployed', 'website', 'operator', 'countries', 'telemetries',
|
||||
'updated', 'citation', 'is_frequency_violator', 'associated_satellites'
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_sat_id(self, obj):
|
||||
"""Returns Satellite sat_id"""
|
||||
return obj.satellite_identifier.sat_id
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT64)
|
||||
def get_norad_cat_id(self, obj):
|
||||
"""Returns Satellite norad_cat_id"""
|
||||
return obj.satellite_entry.norad_cat_id
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT64)
|
||||
def get_norad_follow_id(self, obj):
|
||||
"""Returns Satellite norad_follow_id"""
|
||||
return obj.satellite_entry.norad_follow_id
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_name(self, obj):
|
||||
"""Returns Satellite name"""
|
||||
return obj.satellite_entry.name
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_names(self, obj):
|
||||
"""Returns Satellite alternative names"""
|
||||
return obj.satellite_entry.names
|
||||
|
||||
@extend_schema_field(OpenApiTypes.URI)
|
||||
def get_image(self, obj):
|
||||
"""Returns Satellite image URI"""
|
||||
return str(obj.satellite_entry.image)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_status(self, obj):
|
||||
"""Returns Satellite status text"""
|
||||
return obj.satellite_entry.status
|
||||
|
||||
@extend_schema_field(OpenApiTypes.DATETIME)
|
||||
def get_decayed(self, obj):
|
||||
"""Returns Satellite decayed datetime"""
|
||||
return obj.satellite_entry.decayed
|
||||
|
||||
@extend_schema_field(OpenApiTypes.DATETIME)
|
||||
def get_launched(self, obj):
|
||||
"""Returns Satellite launched datetime"""
|
||||
return obj.satellite_entry.launched
|
||||
|
||||
@extend_schema_field(OpenApiTypes.DATETIME)
|
||||
def get_deployed(self, obj):
|
||||
"""Returns Satellite deployed datetime"""
|
||||
return obj.satellite_entry.deployed
|
||||
|
||||
@extend_schema_field(OpenApiTypes.URI)
|
||||
def get_website(self, obj):
|
||||
"""Returns Satellite website"""
|
||||
return obj.satellite_entry.website
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_operator(self, obj):
|
||||
"""Returns operator text"""
|
||||
return str(obj.satellite_entry.operator)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_countries(self, obj):
|
||||
"""Returns countires"""
|
||||
return obj.satellite_entry.countries_str
|
||||
|
||||
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||
def get_telemetries(self, obj):
|
||||
"""Returns telemetries"""
|
||||
telemetries = SatTelemetrySerializer(obj.telemetries, many=True, read_only=True)
|
||||
return telemetries.data
|
||||
|
||||
@extend_schema_field(OpenApiTypes.DATETIME)
|
||||
def get_updated(self, obj):
|
||||
"""Returns Satellite decayed datetime"""
|
||||
return obj.satellite_entry.reviewed
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_citation(self, obj):
|
||||
"""Returns Satellite decayed datetime"""
|
||||
return obj.satellite_entry.citation
|
||||
|
||||
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||
def get_associated_satellites(self, obj):
|
||||
"""Returns Satellite IDs that are associated with the Satellite"""
|
||||
return [
|
||||
merged_satellite.satellite_identifier.sat_id
|
||||
for merged_satellite in obj.associated_with.all()
|
||||
]
|
||||
|
||||
@extend_schema_field(OpenApiTypes.BOOL)
|
||||
def get_is_frequency_violator(self, obj):
|
||||
"""Returns if there is a frequency violation"""
|
||||
return obj.has_bad_transmitter
|
||||
|
||||
|
||||
class TransmitterEntrySerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB TransmitterEntry API Serializer"""
|
||||
class Meta:
|
||||
model = TransmitterEntry
|
||||
fields = (
|
||||
'uuid', 'description', 'status', 'type', 'uplink_low', 'uplink_high', 'uplink_drift',
|
||||
'downlink_low', 'downlink_high', 'downlink_drift', 'downlink_mode', 'uplink_mode',
|
||||
'invert', 'baud', 'satellite', 'citation', 'service', 'iaru_coordination',
|
||||
'iaru_coordination_url', 'itu_notification', 'created_by'
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Transmitter Example 1',
|
||||
summary='Example: Transmitter API response',
|
||||
value={
|
||||
'uuid': 'eozSf5mKyzNxoascs8V4bV',
|
||||
'description': 'Mode V/U FM - Voice Repeater',
|
||||
'alive': True,
|
||||
'type': 'Transceiver',
|
||||
'uplink_low': 145990000,
|
||||
'uplink_high': None,
|
||||
'uplink_drift': None,
|
||||
'downlink_low': 437800000,
|
||||
'downlink_high': None,
|
||||
'downlink_drift': None,
|
||||
'mode': 'FM',
|
||||
'mode_id': 1,
|
||||
'uplink_mode': 'FM',
|
||||
'invert': False,
|
||||
'baud': None,
|
||||
'norad_cat_id': 25544,
|
||||
'status': 'active',
|
||||
'updated': '2020-09-03T13:14:41.552071Z',
|
||||
'citation': 'https://www.ariss.org/press-releases/september-2-2020',
|
||||
'service': 'Amateur',
|
||||
'iaru_coordination': '',
|
||||
'iaru_coordination_url': '',
|
||||
'itu_notification': ''
|
||||
},
|
||||
response_only=True, # signal that example only applies to responses
|
||||
),
|
||||
]
|
||||
)
|
||||
class TransmitterSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB Transmitter API Serializer"""
|
||||
sat_id = serializers.SerializerMethodField()
|
||||
norad_cat_id = serializers.SerializerMethodField()
|
||||
norad_follow_id = serializers.SerializerMethodField()
|
||||
mode = serializers.SerializerMethodField()
|
||||
mode_id = serializers.SerializerMethodField()
|
||||
uplink_mode = serializers.SerializerMethodField()
|
||||
alive = serializers.SerializerMethodField()
|
||||
updated = serializers.DateTimeField(source='reviewed')
|
||||
frequency_violation = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Transmitter
|
||||
fields = ('uuid', 'description', 'alive', 'type', 'uplink_low', 'uplink_high',
|
||||
'uplink_drift', 'downlink_low', 'downlink_high', 'downlink_drift',
|
||||
'mode_id', 'invert', 'baud', 'norad_cat_id')
|
||||
fields = (
|
||||
'uuid', 'description', 'alive', 'type', 'uplink_low', 'uplink_high', 'uplink_drift',
|
||||
'downlink_low', 'downlink_high', 'downlink_drift', 'mode', 'mode_id', 'uplink_mode',
|
||||
'invert', 'baud', 'sat_id', 'norad_cat_id', 'norad_follow_id', 'status', 'updated',
|
||||
'citation', 'service', 'iaru_coordination', 'iaru_coordination_url',
|
||||
'itu_notification', 'frequency_violation'
|
||||
)
|
||||
|
||||
# Keeping alive field for compatibility issues
|
||||
@extend_schema_field(OpenApiTypes.BOOL)
|
||||
def get_alive(self, obj):
|
||||
"""Returns transmitter status"""
|
||||
return obj.status == TRANSMITTER_STATUS[0]
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def get_mode_id(self, obj):
|
||||
"""Returns downlink mode id"""
|
||||
try:
|
||||
return obj.mode.id
|
||||
except Exception:
|
||||
return obj.downlink_mode.id
|
||||
except AttributeError: # rare chance that this happens in prod
|
||||
return None
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def get_mode(self, obj):
|
||||
"""Returns downlink mode name"""
|
||||
try:
|
||||
return obj.downlink_mode.name
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def get_uplink_mode(self, obj):
|
||||
"""Returns uplink mode name"""
|
||||
try:
|
||||
return obj.uplink_mode.name
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT64)
|
||||
def get_norad_cat_id(self, obj):
|
||||
return obj.satellite.norad_cat_id
|
||||
"""Returns Satellite NORAD ID"""
|
||||
try:
|
||||
return obj.satellite.satellite_entry.norad_cat_id
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT64)
|
||||
def get_norad_follow_id(self, obj):
|
||||
"""Returns Satellite NORAD ID following initial determination"""
|
||||
try:
|
||||
return obj.satellite.satellite_entry.norad_follow_id
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_sat_id(self, obj):
|
||||
"""Returns Satellite NORAD ID"""
|
||||
try:
|
||||
return obj.satellite.satellite_identifier.sat_id
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
@extend_schema_field(OpenApiTypes.BOOL)
|
||||
def get_frequency_violation(self, obj):
|
||||
"""Returns if there is a frequency violation"""
|
||||
return obj.bad_transmitter
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'TLE Example 1',
|
||||
summary='Example: TLE API response',
|
||||
value={
|
||||
'tle0': '0 ISS (ZARYA)',
|
||||
'tle1': '1 25544U 98067A 21009.90234038 .00001675 00000-0 38183-4 0 9997',
|
||||
'tle2': '2 25544 51.6464 45.6388 0000512 205.3232 213.2158 15.49275327264062',
|
||||
'tle_source': 'undisclosed',
|
||||
'norad_cat_id': 25544,
|
||||
'updated': '2021-01-09T22:46:37.781923+0000'
|
||||
},
|
||||
response_only=True, # signal that example only applies to responses
|
||||
),
|
||||
]
|
||||
)
|
||||
class LatestTleSetSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB LatestTleSet API Serializer"""
|
||||
|
||||
sat_id = serializers.SerializerMethodField()
|
||||
norad_cat_id = serializers.SerializerMethodField()
|
||||
tle0 = serializers.SerializerMethodField()
|
||||
tle1 = serializers.SerializerMethodField()
|
||||
tle2 = serializers.SerializerMethodField()
|
||||
tle_source = serializers.SerializerMethodField()
|
||||
updated = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = LatestTleSet
|
||||
fields = ('tle0', 'tle1', 'tle2', 'tle_source', 'sat_id', 'norad_cat_id', 'updated')
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_sat_id(self, obj):
|
||||
"""Returns Satellite Satellite Identifier"""
|
||||
return obj.satellite.satellite_identifier.sat_id
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT64)
|
||||
def get_norad_cat_id(self, obj):
|
||||
"""Returns Satellite NORAD ID"""
|
||||
return obj.satellite.satellite_entry.norad_cat_id
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_tle0(self, obj):
|
||||
"""Returns TLE line 0"""
|
||||
return obj.tle0
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_tle1(self, obj):
|
||||
"""Returns TLE line 1"""
|
||||
return obj.tle1
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_tle2(self, obj):
|
||||
"""Returns TLE line 2"""
|
||||
return obj.tle2
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_tle_source(self, obj):
|
||||
"""Returns TLE source"""
|
||||
return obj.tle_source
|
||||
|
||||
@extend_schema_field(OpenApiTypes.DATETIME)
|
||||
def get_updated(self, obj):
|
||||
"""Returns TLE updated datetime"""
|
||||
return obj.updated.strftime('%Y-%m-%dT%H:%M:%S.%f%z')
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
exclude_fields=('app_source', 'observer', 'timestamp'),
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Telemetry Example 1',
|
||||
summary='Example: retrieving a single Telemetry frame',
|
||||
description='This is an example response for retrieving a single data frame',
|
||||
value={
|
||||
'norad_cat_id': 40379,
|
||||
'transmitter': None,
|
||||
'app_source': 'network',
|
||||
'decoded': 'influxdb',
|
||||
'frame': '968870A6A0A66086A240404040E103F0ABCD0000004203F500B475E215EA5FA0040C000B'
|
||||
'000900010025008E55EE7B64650100000000AE4D07005D660F007673340000C522370067076507FD0'
|
||||
'C60002700FE0CC50E0D00AD0E0B069007BD0E0E00650D21001400FE0C910054007007690D8700FC0C'
|
||||
'BA00E40743001C0F140077077807D7078E00120F240068076D07DA0A74003D0F2500830780077A0AC'
|
||||
'401490F960070077207FDFC9F079507950700C03B0015009AFF6900C8FFE0FFA700EBFF3A00F200F3'
|
||||
'FF02016D0A590A0D0AE3099B0C830CB50DA70D9D06CC0043009401B8338B334C20001000000000009'
|
||||
'F02000003000000FF723D00BEFFFFFFFF2E89B0151C00',
|
||||
'observer': 'KB9JHU-EM69uf',
|
||||
'timestamp': '2021-01-05T22:28:09Z'
|
||||
},
|
||||
response_only=True, # signal that example only applies to responses
|
||||
),
|
||||
]
|
||||
)
|
||||
class TelemetrySerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB Telemetry API Serializer"""
|
||||
sat_id = serializers.SerializerMethodField()
|
||||
norad_cat_id = serializers.SerializerMethodField()
|
||||
transmitter = serializers.SerializerMethodField()
|
||||
schema = serializers.SerializerMethodField()
|
||||
decoded = serializers.SerializerMethodField()
|
||||
frame = serializers.SerializerMethodField()
|
||||
associated_satellites = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = DemodData
|
||||
fields = ('norad_cat_id', 'transmitter', 'source', 'schema', 'decoded', 'frame',
|
||||
'observer', 'timestamp')
|
||||
fields = (
|
||||
'sat_id', 'norad_cat_id', 'transmitter', 'app_source', 'decoded', 'frame', 'observer',
|
||||
'timestamp', 'version', 'observation_id', 'station_id', 'associated_satellites'
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_sat_id(self, obj):
|
||||
"""Returns Satellite Identifier"""
|
||||
if obj.satellite.associated_satellite:
|
||||
return obj.satellite.associated_satellite.satellite_identifier.sat_id
|
||||
return obj.satellite.satellite_identifier.sat_id
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT64)
|
||||
def get_norad_cat_id(self, obj):
|
||||
return obj.satellite.norad_cat_id
|
||||
"""Returns Satellite NORAD ID"""
|
||||
return obj.satellite.satellite_entry.norad_cat_id
|
||||
|
||||
@extend_schema_field(OpenApiTypes.UUID)
|
||||
def get_transmitter(self, obj):
|
||||
"""Returns Transmitter UUID"""
|
||||
try:
|
||||
return obj.transmitter.uuid
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
def get_schema(self, obj):
|
||||
try:
|
||||
return obj.payload_telemetry.schema
|
||||
except Exception:
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_decoded(self, obj):
|
||||
"""Returns the payload_decoded field"""
|
||||
return obj.payload_decoded
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_frame(self, obj):
|
||||
"""Returns the payload frame"""
|
||||
return obj.display_frame()
|
||||
|
||||
@extend_schema_field(OpenApiTypes.OBJECT)
|
||||
def get_associated_satellites(self, obj):
|
||||
"""Returns Satellite IDs that are associated with the Satellite"""
|
||||
satellite = obj.satellite
|
||||
if satellite.associated_satellite:
|
||||
satellite = satellite.associated_satellite
|
||||
return [
|
||||
merged_satellite.satellite_identifier.sat_id
|
||||
for merged_satellite in satellite.associated_with.all()
|
||||
]
|
||||
|
||||
# @extend_schema_field(OpenApiTypes.STR)
|
||||
# def get_version(self, obj):
|
||||
# """Returns the payload version"""
|
||||
# return obj.version
|
||||
|
||||
|
||||
class SidsSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB SiDS API Serializer"""
|
||||
class Meta:
|
||||
model = DemodData
|
||||
fields = ('satellite', 'payload_frame', 'station', 'lat', 'lng', 'timestamp', 'source')
|
||||
fields = (
|
||||
'satellite', 'payload_frame', 'station', 'lat', 'lng', 'timestamp', 'app_source',
|
||||
'observer', 'version', 'observation_id', 'station_id'
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'View Artifact Example 1',
|
||||
summary='Example: retrieving a specific artifact',
|
||||
description='This is an example response when requesting a specific artifact '
|
||||
'previously uploaded to DB',
|
||||
value={
|
||||
'id': 1337,
|
||||
'network_obs_id': 3376466,
|
||||
'artifact_file': 'http://db-dev.satnogs.org/media/artifacts/bba35b2d-76cc-4a8f-'
|
||||
'9b8a-4a2ecb09c6df.h5'
|
||||
},
|
||||
status_codes=['200'],
|
||||
response_only=True, # signal that example only applies to responses
|
||||
),
|
||||
]
|
||||
)
|
||||
class ArtifactSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS DB Artifacts API Serializer"""
|
||||
class Meta:
|
||||
model = Artifact
|
||||
fields = ('id', 'network_obs_id', 'artifact_file')
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'New Artifact Example 1',
|
||||
summary='Example: uploading artifact',
|
||||
description='This is an example response after successfully uploading an artifact '
|
||||
'file. The ID of the artifact is returned',
|
||||
value={
|
||||
'id': 1337,
|
||||
},
|
||||
status_codes=['200', '201'],
|
||||
response_only=True, # signal that example only applies to responses
|
||||
),
|
||||
]
|
||||
)
|
||||
class NewArtifactSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS Network New Artifact API Serializer"""
|
||||
def validate(self, attrs):
|
||||
"""Validates data of incoming artifact"""
|
||||
|
||||
try:
|
||||
with h5py.File(self.initial_data['artifact_file'], 'r') as h5_file:
|
||||
if 'artifact_version' not in h5_file.attrs:
|
||||
raise serializers.ValidationError(
|
||||
'Not a valid SatNOGS Artifact.', code='invalid'
|
||||
)
|
||||
except (OSError, MultiValueDictKeyError) as error:
|
||||
raise serializers.ValidationError(
|
||||
'Not a valid HDF5 file: {}'.format(error), code='invalid'
|
||||
)
|
||||
|
||||
return attrs
|
||||
|
||||
class Meta:
|
||||
model = Artifact
|
||||
fields = ('artifact_file', )
|
||||
|
|
200
db/api/tests.py
200
db/api/tests.py
|
@ -1,9 +1,10 @@
|
|||
"""SatNOGS DB API test suites"""
|
||||
import pytest
|
||||
|
||||
from rest_framework import status
|
||||
from django.contrib.auth.models import User # pylint: disable=E5142
|
||||
from django.test import TestCase
|
||||
from rest_framework import status
|
||||
|
||||
from db.base.tests import ModeFactory, SatelliteFactory, TransmitterFactory, DemodDataFactory
|
||||
from db.base.tests import DemodDataFactory, ModeFactory, SatelliteFactory, TransmitterFactory
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
|
@ -18,12 +19,13 @@ class ModeViewApiTest(TestCase):
|
|||
self.mode.save()
|
||||
|
||||
def test_list(self):
|
||||
"""Test the API modes list"""
|
||||
response = self.client.get('/api/modes/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_retrieve(self):
|
||||
response = self.client.get('/api/modes/{0}/'.format(self.mode.id),
|
||||
format='json')
|
||||
"""Test the API mode retrieval"""
|
||||
response = self.client.get('/api/modes/{0}/'.format(self.mode.id), format='json')
|
||||
self.assertContains(response, self.mode.name)
|
||||
|
||||
|
||||
|
@ -39,13 +41,40 @@ class SatelliteViewApiTest(TestCase):
|
|||
self.satellite.save()
|
||||
|
||||
def test_list(self):
|
||||
"""Test the Satellite API listing"""
|
||||
response = self.client.get('/api/satellites/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_retrieve(self):
|
||||
response = self.client.get('/api/satellites/{0}/'.format(self.satellite.norad_cat_id),
|
||||
format='json')
|
||||
self.assertContains(response, self.satellite.name)
|
||||
def test_retrieve_with_norad_id(self):
|
||||
"""Test the Satellite API retrieval with NORAD ID"""
|
||||
response = self.client.get(
|
||||
'/api/satellites/{0}/'.format(self.satellite.satellite_entry.norad_cat_id),
|
||||
format='json'
|
||||
)
|
||||
self.assertContains(response, self.satellite.satellite_entry.name)
|
||||
|
||||
def test_retrieve_with_satellite_id(self):
|
||||
"""Test the Satellite API retrieval with Satellite Identifier"""
|
||||
response = self.client.get(
|
||||
'/api/satellites/{0}/'.format(self.satellite.satellite_identifier.sat_id),
|
||||
format='json'
|
||||
)
|
||||
self.assertContains(response, self.satellite.satellite_entry.name)
|
||||
|
||||
def test_retrieve_nonexistent_satellite(self):
|
||||
"""Tests for a non existent satellite"""
|
||||
response = self.client.get('/api/satellites/{0}/'.format('BADBADBADBAD'), format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def test_retrieve_jsonld_satellites(self):
|
||||
"""Tests the return of a satellite via JSONLD browsable renderer"""
|
||||
response = self.client.get('/api/satellites/?format=json-ld')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_retrieve_browse_jsonld_satellites(self):
|
||||
"""Tests the return of a satellite via JSONLD browsable renderer"""
|
||||
response = self.client.get('/api/satellites/?format=browse-json-ld')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
|
@ -56,35 +85,174 @@ class TransmitterViewApiTest(TestCase):
|
|||
transmitter = None
|
||||
|
||||
def setUp(self):
|
||||
self.transmitter = TransmitterFactory()
|
||||
TransmitterFactory.create_batch(size=50)
|
||||
self.transmitter = TransmitterFactory(status='active')
|
||||
self.transmitter.uuid = 'test'
|
||||
self.transmitter.save()
|
||||
|
||||
def test_list(self):
|
||||
"""Test the Transmitter API listing"""
|
||||
response = self.client.get('/api/transmitters/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_list_active(self):
|
||||
"""Test the Transmitter API listing with active filter"""
|
||||
response = self.client.get('/api/transmitters/?Alive=true&format=json')
|
||||
self.assertContains(response, '\"active\"')
|
||||
|
||||
def test_list_inactive(self):
|
||||
"""Test the Transmitter API listing with inactive"""
|
||||
response = self.client.get('/api/transmitters/?Alive=false&format=json')
|
||||
self.assertContains(response, '\"inactive\"')
|
||||
|
||||
def test_retrieve(self):
|
||||
response = self.client.get('/api/transmitters/{0}/'.format(self.transmitter.uuid),
|
||||
format='json')
|
||||
"""Test the Transmitter API retrieval"""
|
||||
response = self.client.get(
|
||||
'/api/transmitters/{0}/'.format(self.transmitter.uuid), format='json'
|
||||
)
|
||||
self.assertContains(response, self.transmitter.description)
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
@pytest.mark.usefixtures('celery_session_app')
|
||||
@pytest.mark.usefixtures('celery_session_worker')
|
||||
class TelemetryViewApiTest(TestCase):
|
||||
"""
|
||||
Tests the Telemetry View API
|
||||
"""
|
||||
datum = None
|
||||
satellite = None
|
||||
|
||||
frame = '60A060A0A46E609C8262A6A640E082A0A4A682A86103F02776261C6C201C5'
|
||||
frame += '3495D41524953532D496E7465726E6174696F6E616C2053706163652053746174696F6E3D0D'
|
||||
|
||||
def setUp(self):
|
||||
self.datum = DemodDataFactory()
|
||||
self.datum.save()
|
||||
self.satellite = SatelliteFactory()
|
||||
self.satellite.save()
|
||||
|
||||
def test_list(self):
|
||||
response = self.client.get('/api/telemetry/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
def test_list_anonymous(self):
|
||||
"""Test the Telemetry API listing"""
|
||||
response = self.client.get('/api/telemetry/')
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_retrieve(self):
|
||||
"""Test the Telemetry API retrieval"""
|
||||
response = self.client.get('/api/telemetry/{0}/'.format(self.datum.id), format='json')
|
||||
self.assertContains(response, self.datum.observer)
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_post(self):
|
||||
"""Test the network posting capability"""
|
||||
norad = self.satellite.satellite_entry.norad_cat_id
|
||||
|
||||
data = {
|
||||
'frame': self.frame,
|
||||
'locator': 'longLat',
|
||||
'latitude': '06.12S',
|
||||
'longitude': '59.34W',
|
||||
'noradID': str(norad),
|
||||
'source': 'T3ST',
|
||||
'timestamp': '2021-03-15T13:14:04.940Z',
|
||||
'version': '1.2.3',
|
||||
'observation_id': '123456789',
|
||||
'satnogs_network': 'true',
|
||||
'station_id': '2'
|
||||
}
|
||||
response = self.client.post('/api/telemetry/', data=data)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
def test_post_numerical_latlng(self):
|
||||
"""Test the SiDS posting capability without a N/S and E/W identifier"""
|
||||
norad = self.satellite.satellite_entry.norad_cat_id
|
||||
|
||||
data = {
|
||||
'frame': self.frame,
|
||||
'locator': 'longLat',
|
||||
'latitude': '06.12',
|
||||
'longitude': '59.34',
|
||||
'noradID': str(norad),
|
||||
'source': 'T3ST',
|
||||
'timestamp': '2021-03-15T13:14:04.940Z'
|
||||
}
|
||||
response = self.client.post('/api/telemetry/', data=data)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
def test_post_new_satellite(self):
|
||||
"""Test the SiDS posting capability while creating a new satellite"""
|
||||
|
||||
data = {
|
||||
'frame': self.frame,
|
||||
'locator': 'longLat',
|
||||
'latitude': '06.12S',
|
||||
'longitude': '59.34W',
|
||||
'noradID': '999999',
|
||||
'source': 'T3ST',
|
||||
'timestamp': '2021-03-15T13:14:04.940Z',
|
||||
'version': '1.2.3'
|
||||
}
|
||||
response = self.client.post('/api/telemetry/', data=data)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
def test_post_bad_new_satellite(self):
|
||||
"""Test the SiDS upload while creating a new satellite with bad NORAD"""
|
||||
|
||||
data = {
|
||||
'frame': self.frame,
|
||||
'locator': 'longLat',
|
||||
'latitude': '06.12S',
|
||||
'longitude': '59.34W',
|
||||
'noradID': 'STR999999',
|
||||
'source': 'T3ST',
|
||||
'timestamp': '2021-03-15T13:14:04.940Z',
|
||||
'version': '1.2.3'
|
||||
}
|
||||
response = self.client.post('/api/telemetry/', data=data)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def test_bad_post(self):
|
||||
"""Test the SiDS posting capability with bad data"""
|
||||
norad = self.satellite.satellite_entry.norad_cat_id
|
||||
|
||||
data = {
|
||||
'frame': '',
|
||||
'locator': 'longLat',
|
||||
'latitude': '206.12S',
|
||||
'longitude': '59.34WE',
|
||||
'noradID': str(norad),
|
||||
'source': '',
|
||||
'timestamp': ''
|
||||
}
|
||||
response = self.client.post('/api/telemetry/', data=data)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
class LoginView(TestCase):
|
||||
"""
|
||||
Tests various API endpoints with authentication
|
||||
"""
|
||||
datum = None
|
||||
|
||||
def setUp(self):
|
||||
DemodDataFactory.create_batch(size=18)
|
||||
self.datum = DemodDataFactory()
|
||||
self.datum.save()
|
||||
self.client.force_login(User.objects.get_or_create(username='testuser')[0])
|
||||
|
||||
def test_auth_telemetry_list_without_filter(self):
|
||||
"""Test the Telemetry API listing and pagination with authentication"""
|
||||
response = self.client.get('/api/telemetry/?page=1')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def test_auth_telemetry_list_with_satellite_filter(self):
|
||||
"""Test the Telemetry API listing and pagination with authentication"""
|
||||
norad_id = self.datum.satellite.satellite_entry.norad_cat_id
|
||||
response = self.client.get('/api/telemetry/?page=1&satellite=' + str(norad_id))
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_auth_telemetry_list_with_sat_id_filter(self):
|
||||
"""Test the Telemetry API listing and pagination with authentication"""
|
||||
sat_id = self.datum.satellite.satellite_identifier.sat_id
|
||||
response = self.client.get('/api/telemetry/?page=1&sat_id=' + sat_id)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
"""SatNOGS DB API throttling classes, django rest framework"""
|
||||
from django.core.cache import cache
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import throttling
|
||||
|
||||
from db.base.models import Satellite
|
||||
|
||||
|
||||
class GetTelemetryAnononymousRateThrottle(throttling.AnonRateThrottle):
|
||||
"""Anonymous GET Throttling for Telemetry API endpoint"""
|
||||
scope = 'get_telemetry_anon'
|
||||
|
||||
def allow_request(self, request, view):
|
||||
if request.method == 'POST':
|
||||
return True
|
||||
return super().allow_request(request, view)
|
||||
|
||||
|
||||
class GetTelemetryUserRateThrottle(throttling.UserRateThrottle):
|
||||
"""User GET Throttling for Telemetry API endpoint"""
|
||||
scope = 'get_telemetry_user'
|
||||
|
||||
def allow_request(self, request, view):
|
||||
if request.method == 'POST':
|
||||
return True
|
||||
return super().allow_request(request, view)
|
||||
|
||||
|
||||
class GetTelemetryViolatorThrottle(throttling.BaseThrottle):
|
||||
"""Violator satellites GET Throttling for Telemetry API endpoint"""
|
||||
scope = 'get_telemetry_violator'
|
||||
|
||||
def allow_request(self, request, view):
|
||||
if request.method == 'POST':
|
||||
return True
|
||||
satellite = request.query_params.get('satellite', None)
|
||||
sat_id = request.query_params.get('sat_id', None)
|
||||
violation = None
|
||||
|
||||
if sat_id:
|
||||
violation = cache.get('violator_' + sat_id)
|
||||
elif satellite:
|
||||
violation = cache.get('violator_' + str(satellite))
|
||||
else:
|
||||
return True
|
||||
|
||||
if violation is None:
|
||||
if sat_id:
|
||||
satellite_obj = get_object_or_404(Satellite, satellite_identifier__sat_id=sat_id)
|
||||
else:
|
||||
satellite_obj = get_object_or_404(
|
||||
Satellite, satellite_entry__norad_cat_id=satellite
|
||||
)
|
||||
if satellite_obj.associated_satellite:
|
||||
satellite_obj = satellite_obj.associated_satellite
|
||||
if satellite_obj.has_bad_transmitter:
|
||||
return cache.add('violator_telemetry_' + str(satellite_obj.id), True, 86400)
|
||||
elif violation['status']:
|
||||
return cache.add('violator_telemetry_' + str(violation['id']), True, 86400)
|
||||
return True
|
|
@ -1,13 +1,24 @@
|
|||
"""SatNOGS DB django rest framework API url routings"""
|
||||
from django.urls import include, path
|
||||
from rest_framework import routers
|
||||
|
||||
from db.api import views
|
||||
|
||||
ROUTER = routers.DefaultRouter()
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
ROUTER.register(r'artifacts', views.ArtifactViewSet)
|
||||
ROUTER.register(r'modes', views.ModeViewSet)
|
||||
ROUTER.register(r'satellites', views.SatelliteViewSet)
|
||||
ROUTER.register(r'transmitters', views.TransmitterViewSet)
|
||||
ROUTER.register(r'telemetry', views.TelemetryViewSet)
|
||||
ROUTER.register(r'tle', views.LatestTleSetViewSet)
|
||||
|
||||
router.register(r'modes', views.ModeView)
|
||||
router.register(r'satellites', views.SatelliteView)
|
||||
router.register(r'transmitters', views.TransmitterView)
|
||||
router.register(r'telemetry', views.TelemetryView)
|
||||
|
||||
api_urlpatterns = router.urls
|
||||
API_URLPATTERNS = [
|
||||
# Keep combatibility by allowing to get satellite object with NORAD
|
||||
# ID.Adding 'basename' value to use it in custom renderers.
|
||||
path(
|
||||
'satellites/<int:satellite_entry__norad_cat_id>/',
|
||||
views.SatelliteViewSet.as_view({'get': 'retrieve'}, basename='latestsatellite')
|
||||
),
|
||||
path('', include(ROUTER.urls))
|
||||
]
|
||||
|
|
722
db/api/views.py
722
db/api/views.py
|
@ -1,82 +1,720 @@
|
|||
from rest_framework import viewsets, mixins, status
|
||||
from rest_framework.parsers import FormParser, FileUploadParser
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
|
||||
"""SatNOGS DB API django rest framework Views"""
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db.models import F
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.timezone import now
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiExample, OpenApiParameter, extend_schema, \
|
||||
extend_schema_view
|
||||
from rest_framework import mixins, status, viewsets
|
||||
from rest_framework.parsers import FileUploadParser, FormParser, MultiPartParser
|
||||
from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from db.api import serializers, filters, pagination
|
||||
from db.base.models import Mode, Satellite, Transmitter, DemodData
|
||||
from db.base.tasks import update_satellite
|
||||
from db.api import filters, pagination, serializers
|
||||
from db.api.parsers import JSONLDParser
|
||||
from db.api.perms import IsAuthenticatedOrOptions, SafeMethodsWithPermission
|
||||
from db.api.renderers import BrowserableJSONLDRenderer, JSONLDRenderer
|
||||
from db.api.throttling import GetTelemetryAnononymousRateThrottle, GetTelemetryUserRateThrottle, \
|
||||
GetTelemetryViolatorThrottle
|
||||
from db.base.helpers import gridsquare
|
||||
from db.base.models import SATELLITE_STATUS, SERVICE_TYPE, TRANSMITTER_STATUS, TRANSMITTER_TYPE, \
|
||||
Artifact, DemodData, LatestTleSet, Mode, Satellite, SatelliteEntry, SatelliteIdentifier, \
|
||||
Transmitter
|
||||
from db.base.tasks import decode_current_frame, publish_current_frame, update_satellite_name
|
||||
|
||||
ISS_EXAMPLE = OpenApiExample('25544 (ISS)', value=25544)
|
||||
|
||||
|
||||
class ModeView(viewsets.ReadOnlyModelViewSet):
|
||||
@extend_schema_view(
|
||||
retrieve=extend_schema(
|
||||
description='Retrieve a single RF Mode from SatNOGS DB based on its ID',
|
||||
),
|
||||
list=extend_schema(description='Retrieve a complete list of RF Modes from SatNOGS DB', )
|
||||
)
|
||||
class ModeViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
|
||||
"""
|
||||
Read-only view into the transmitter modulation modes (RF Modes) currently tracked
|
||||
in the SatNOGS DB database
|
||||
|
||||
For more details on individual RF mode types please [see our wiki][moderef].
|
||||
|
||||
[moderef]: https://wiki.satnogs.org/Category:RF_Modes
|
||||
"""
|
||||
renderer_classes = [
|
||||
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
|
||||
]
|
||||
queryset = Mode.objects.all()
|
||||
serializer_class = serializers.ModeSerializer
|
||||
|
||||
|
||||
class SatelliteView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Satellite.objects.all()
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
description='Retrieve a full or filtered list of satellites in SatNOGS DB',
|
||||
parameters=[
|
||||
# drf-spectacular does not currently recognize the in_orbit filter as a
|
||||
# bool, forcing it here. See drf-spectacular#234
|
||||
OpenApiParameter(
|
||||
name='in_orbit',
|
||||
description='Filter by satellites currently in orbit (True) or those that have \
|
||||
decayed (False)',
|
||||
required=False,
|
||||
type=bool
|
||||
),
|
||||
OpenApiParameter(
|
||||
name='status',
|
||||
description='Filter by satellite status: ' + ' '.join(SATELLITE_STATUS),
|
||||
required=False,
|
||||
type=OpenApiTypes.STR
|
||||
),
|
||||
OpenApiParameter(
|
||||
name='norad_cat_id',
|
||||
description='Select a satellite by its NORAD-assigned identifier',
|
||||
examples=[ISS_EXAMPLE],
|
||||
),
|
||||
],
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
description='Retrieve details on a single satellite in SatNOGS DB',
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
'satellite_identifier__sat_id',
|
||||
OpenApiTypes.STR,
|
||||
OpenApiParameter.PATH,
|
||||
description='Select a satellite by its Satellite Identifier',
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
class SatelliteViewSet( # pylint: disable=R0901
|
||||
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
|
||||
viewsets.GenericViewSet):
|
||||
"""
|
||||
View into the Satellite entities in the SatNOGS DB database
|
||||
"""
|
||||
renderer_classes = [
|
||||
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
|
||||
]
|
||||
parser_classes = [JSONLDParser]
|
||||
queryset = Satellite.objects.filter(
|
||||
associated_satellite__isnull=True, satellite_entry__approved=True
|
||||
).prefetch_related('associated_with', 'telemetries')
|
||||
serializer_class = serializers.SatelliteSerializer
|
||||
filter_class = filters.SatelliteViewFilter
|
||||
lookup_field = 'norad_cat_id'
|
||||
filterset_class = filters.SatelliteViewFilter
|
||||
lookup_field = 'satellite_identifier__sat_id'
|
||||
|
||||
def get_object(self):
|
||||
queryset = self.get_queryset()
|
||||
# Apply any filter backends
|
||||
queryset = self.filter_queryset(queryset)
|
||||
|
||||
# In case user uses NORAD ID for getting satellite
|
||||
if 'satellite_entry__norad_cat_id' in self.kwargs:
|
||||
norad_cat_id = self.kwargs['satellite_entry__norad_cat_id']
|
||||
return get_object_or_404(queryset, satellite_entry__norad_cat_id=norad_cat_id)
|
||||
|
||||
# Getting satellite by using Satellite Identifier
|
||||
sat_id = self.kwargs['satellite_identifier__sat_id']
|
||||
try:
|
||||
return queryset.get(satellite_identifier__sat_id=sat_id)
|
||||
except Satellite.DoesNotExist:
|
||||
return get_object_or_404(
|
||||
queryset, associated_with__satellite_identifier__sat_id=sat_id
|
||||
)
|
||||
|
||||
def create(self, request, *args, **kwargs): # noqa: C901; pylint: disable=R0911,R0912,R0915
|
||||
"""
|
||||
Creates a satellite suggestion.
|
||||
"""
|
||||
satellites_data = []
|
||||
for satellite_entry in request.data['@graph']:
|
||||
if 'satellite' not in satellite_entry:
|
||||
data = 'Satellite Entry without "satellite" key'
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not satellite_entry['satellite']:
|
||||
data = 'One or more of the required fields are missing.\n Required fields: \
|
||||
name, status, citation'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
satellite = satellite_entry['satellite']
|
||||
create_satellite_identifier = False
|
||||
|
||||
satellite_data = {}
|
||||
if "@id" not in satellite:
|
||||
data = 'Missing "@id" for one or more entries'
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if 'sat_id' in satellite:
|
||||
if isinstance(satellite['sat_id'], list):
|
||||
data = 'Multiple values for "http://schema.org/identifier" or multiple \
|
||||
entries with the same "@id" and different \
|
||||
"http://schema.org/identifier" values'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
sat_id = satellite['sat_id']
|
||||
try:
|
||||
satellite_object = Satellite.objects.get(satellite_identifier__sat_id=sat_id)
|
||||
satellite_data['satellite_identifier'
|
||||
] = satellite_object.satellite_entry.satellite_identifier.pk
|
||||
satellite_data['norad_follow_id'
|
||||
] = satellite_object.satellite_entry.norad_follow_id
|
||||
satellite_data['description'] = satellite_object.satellite_entry.description
|
||||
satellite_data['dashboard_url'
|
||||
] = satellite_object.satellite_entry.dashboard_url
|
||||
satellite_data['image'] = satellite_object.satellite_entry.image
|
||||
satellite_data['decayed'] = satellite_object.satellite_entry.decayed
|
||||
satellite_data['countries'] = satellite_object.satellite_entry.countries
|
||||
satellite_data['website'] = satellite_object.satellite_entry.website
|
||||
satellite_data['launched'] = satellite_object.satellite_entry.launched
|
||||
satellite_data['deployed'] = satellite_object.satellite_entry.deployed
|
||||
satellite_data['operator'] = satellite_object.satellite_entry.operator
|
||||
except Satellite.DoesNotExist:
|
||||
try:
|
||||
satellite_identifier = SatelliteIdentifier.objects.get(sat_id=sat_id)
|
||||
satellite_data['satellite_identifier'] = satellite_identifier.pk
|
||||
except SatelliteIdentifier.DoesNotExist:
|
||||
satellite_identifier = SatelliteIdentifier.objects.create(sat_id=sat_id)
|
||||
satellite_data['satellite_identifier'] = satellite_identifier.pk
|
||||
|
||||
else:
|
||||
create_satellite_identifier = True
|
||||
|
||||
if isinstance(satellite['status'], list):
|
||||
data = 'Multiple values for "https://schema.space/metasat/status" or multiple \
|
||||
entries with the same "@id" and different \
|
||||
"https://schema.space/metasat/status" values'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if isinstance(satellite['name'], list):
|
||||
data = 'Multiple values for "https://schema.space/metasat/name" or multiple \
|
||||
entries with the same "@id" and different \
|
||||
"https://schema.space/metasat/name" values'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if isinstance(satellite['citation'], list):
|
||||
data = 'Multiple values for "https://schema.org/citation" or multiple \
|
||||
entries with the same "@id" and different \
|
||||
"https://schema.org/citation" values'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
satellite_data['name'] = satellite['name']
|
||||
satellite_data['status'] = satellite['status']
|
||||
satellite_data['citation'] = satellite['citation']
|
||||
satellite_data['created_by'] = request.user.pk
|
||||
|
||||
if 'norad_cat_id' in satellite:
|
||||
if isinstance(satellite['norad_cat_id'], list):
|
||||
data = 'Multiple values for "https://schema.space/metasat/noradID" or \
|
||||
multiple entries with the same "@id" and different \
|
||||
"https://schema.space/metasat/noradId" values'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
satellite_data['norad_cat_id'] = satellite['norad_cat_id']
|
||||
|
||||
if 'names' in satellite:
|
||||
if isinstance(satellite['norad_cat_id'], list):
|
||||
satellite_data['names'] = '\r\n'.join(satellite['names'])
|
||||
else:
|
||||
satellite_data['names'] = satellite['names']
|
||||
|
||||
if create_satellite_identifier:
|
||||
satellite_identifier = SatelliteIdentifier.objects.create()
|
||||
satellite_data['satellite_identifier'] = satellite_identifier.pk
|
||||
|
||||
satellites_data.append(satellite_data)
|
||||
|
||||
serializer = serializers.SatelliteEntrySerializer(
|
||||
data=satellites_data, many=True, allow_empty=True
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
else:
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class TransmitterView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Transmitter.objects.all()
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name='satellite__norad_cat_id',
|
||||
description='NORAD ID of a satellite to filter telemetry data for',
|
||||
examples=[ISS_EXAMPLE],
|
||||
),
|
||||
OpenApiParameter(
|
||||
name='status',
|
||||
description='Filter by transmitter status: ' + ' '.join(TRANSMITTER_STATUS),
|
||||
required=False,
|
||||
type=OpenApiTypes.STR,
|
||||
examples=[OpenApiExample('active', value='\'active\'')]
|
||||
),
|
||||
OpenApiParameter(
|
||||
name='service',
|
||||
description='Filter by transmitter service: ' + ' '.join(SERVICE_TYPE),
|
||||
required=False,
|
||||
type=OpenApiTypes.STR,
|
||||
examples=[OpenApiExample('Amateur', value='\'Amateur\'')]
|
||||
),
|
||||
OpenApiParameter(
|
||||
name='type',
|
||||
description='Filter by transmitter type: ' + ' '.join(TRANSMITTER_TYPE),
|
||||
required=False,
|
||||
type=OpenApiTypes.STR,
|
||||
examples=[OpenApiExample('Transmitter', value='\'Transmitter\'')]
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
class TransmitterViewSet( # pylint: disable=R0901
|
||||
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
|
||||
viewsets.GenericViewSet):
|
||||
"""
|
||||
View into the Transmitter entities in the SatNOGS DB database.
|
||||
Transmitters are inclusive of Transceivers and Transponders
|
||||
"""
|
||||
renderer_classes = [
|
||||
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
|
||||
]
|
||||
parser_classes = [JSONLDParser]
|
||||
queryset = Transmitter.objects.filter(
|
||||
satellite__satellite_entry__approved=True, satellite__associated_satellite__isnull=True
|
||||
).exclude(status='invalid')
|
||||
serializer_class = serializers.TransmitterSerializer
|
||||
filter_class = filters.TransmitterViewFilter
|
||||
filterset_class = filters.TransmitterViewFilter
|
||||
lookup_field = 'uuid'
|
||||
|
||||
def create(self, request, *args, **kwargs): # noqa: C901; pylint: disable=R0911,R0912,R0915
|
||||
"""
|
||||
Creates a transmitter suggestion.
|
||||
"""
|
||||
transmitters_data = []
|
||||
for transmitter_entry in request.data['@graph']:
|
||||
if 'transmitter' not in transmitter_entry:
|
||||
data = 'Transmitter Entry without "transmitter" key'
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not transmitter_entry['transmitter']:
|
||||
data = 'One or more of the required fields are missing.\n Required fields: \
|
||||
description, status, citation, service, satellite'
|
||||
|
||||
class TelemetryView(viewsets.ModelViewSet, mixins.CreateModelMixin):
|
||||
queryset = DemodData.objects.all()
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
transmitter = transmitter_entry['transmitter']
|
||||
|
||||
transmitter_data = {}
|
||||
if "@id" not in transmitter:
|
||||
data = 'Missing "@id" for one or more entries'
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if 'uuid' in transmitter:
|
||||
if isinstance(transmitter['uuid'], list):
|
||||
data = 'Multiple values for "http://schema.org/identifier" or multiple \
|
||||
entries with the same "@id" and different \
|
||||
"http://schema.org/identifier" values'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
transmitter_uuid = transmitter['uuid']
|
||||
if Transmitter.objects.filter(uuid=transmitter_uuid).exists():
|
||||
transmitter_data['uuid'] = transmitter_uuid
|
||||
|
||||
transmitter_data['description'] = transmitter['description']
|
||||
transmitter_data['status'] = transmitter['status']
|
||||
transmitter_data['citation'] = transmitter['citation']
|
||||
transmitter_data['service'] = transmitter['service']
|
||||
transmitter_data['created_by'] = request.user.pk
|
||||
|
||||
try:
|
||||
if transmitter['satellite']:
|
||||
if isinstance(transmitter['satellite'], list):
|
||||
data = 'Multiple values for "https://schema.space/metasat/satellite" \
|
||||
or multiple entries with the same "@id" and different \
|
||||
"https://schema.space/metasat/satellite" values'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
transmitter_data['satellite'] = Satellite.objects.get(
|
||||
satellite_entry__norad_cat_id=transmitter['satellite']['norad_cat_id']
|
||||
).pk
|
||||
else:
|
||||
data = 'Missing NORAD ID value for Satellite'
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
except Satellite.DoesNotExist:
|
||||
data = 'Unknown NORAD ID: {}'.format(transmitter['satellite']['norad_cat_id'])
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if 'baud' in transmitter:
|
||||
transmitter_data['baud'] = transmitter['baud']
|
||||
|
||||
if 'invert' in transmitter:
|
||||
transmitter_data['invert'] = transmitter['invert']
|
||||
|
||||
if 'uplink' not in transmitter and 'downlink' not in transmitter:
|
||||
data = 'Missing "https://schema.space/metasat/uplink" or \
|
||||
"https://schema.space/metasat/downlink"'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if 'uplink' in transmitter:
|
||||
if isinstance(transmitter['uplink'], list):
|
||||
data = 'Multiple values for "https://schema.space/metasat/uplink" or multiple \
|
||||
entries with the same "@id" and different \
|
||||
"https://schema.space/metasat/uplink" values'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if 'frequency' not in transmitter['uplink']:
|
||||
data = 'Missing "https://schema.space/metasat/frequency" from \
|
||||
"https://schema.space/metasat/uplink" value'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if isinstance(transmitter['uplink']['frequency'], int):
|
||||
transmitter_data['type'] = 'Transceiver'
|
||||
transmitter_data['uplink_low'] = transmitter['uplink']['frequency']
|
||||
else:
|
||||
transmitter_data['type'] = 'Transponder'
|
||||
if 'minimum' not in transmitter['uplink'][
|
||||
'frequency'] or 'maximum' not in transmitter['uplink']['frequency']:
|
||||
data = 'Missing "https://schema.org/minimum" or \
|
||||
"https://schema.org/maximum" from \
|
||||
"https://schema.space/metasat/frequency" value of \
|
||||
"https://schema.space/metasat/uplink"'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
transmitter_data['uplink_low'] = transmitter['uplink']['frequency']['minimum']
|
||||
transmitter_data['uplink_high'] = transmitter['uplink']['frequency']['maximum']
|
||||
if 'mode' in transmitter['uplink']:
|
||||
try:
|
||||
transmitter_data['uplink_mode'] = Mode.objects.get(
|
||||
name=transmitter['uplink']['mode']
|
||||
).pk
|
||||
except Mode.DoesNotExist:
|
||||
data = 'Unknown Mode: {}'.format(transmitter['uplink']['mode'])
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if 'drift' in transmitter['uplink']:
|
||||
transmitter_data['uplink_drift'] = transmitter['uplink']['drift']
|
||||
else:
|
||||
transmitter_data['type'] = 'Transmitter'
|
||||
|
||||
if 'downlink' in transmitter:
|
||||
if isinstance(transmitter['downlink'], list):
|
||||
data = 'Multiple values for "https://schema.space/metasat/downlink" or \
|
||||
multiple entries with the same "@id" and different \
|
||||
"https://schema.space/metasat/downlink" values'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if 'frequency' not in transmitter['downlink']:
|
||||
data = 'Missing "https://schema.space/metasat/frequency" from \
|
||||
"https://schema.space/metasat/downlink" value'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if isinstance(transmitter['downlink']['frequency'],
|
||||
int) and not transmitter_data['type'] == 'Transponder':
|
||||
transmitter_data['downlink_low'] = transmitter['downlink']['frequency']
|
||||
elif transmitter_data['type'] == 'Transponder':
|
||||
if 'minimum' not in transmitter['downlink'][
|
||||
'frequency'] or 'maximum' not in transmitter['downlink']['frequency']:
|
||||
data = 'Missing "https://schema.org/minimum" or \
|
||||
"https://schema.org/maximum" from \
|
||||
"https://schema.space/metasat/frequency" value of \
|
||||
"https://schema.space/metasat/downlink"'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
transmitter_data['downlink_low'] = transmitter['downlink']['frequency'][
|
||||
'minimum']
|
||||
transmitter_data['downlink_high'] = transmitter['downlink']['frequency'][
|
||||
'maximum']
|
||||
else:
|
||||
data = 'Expected integer for "https://schema.space/metasat/frequency" value \
|
||||
of "https://schema.space/metasat/downlink"'
|
||||
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if 'mode' in transmitter['downlink']:
|
||||
try:
|
||||
transmitter_data['downlink_mode'] = Mode.objects.get(
|
||||
name=transmitter['downlink']['mode']
|
||||
).pk
|
||||
except Mode.DoesNotExist:
|
||||
data = 'Unknown Mode: {}'.format(transmitter['downlink']['mode'])
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
if 'drift' in transmitter['downlink']:
|
||||
transmitter_data['downlink_drift'] = transmitter['downlink']['drift']
|
||||
transmitters_data.append(transmitter_data)
|
||||
|
||||
serializer = serializers.TransmitterEntrySerializer(
|
||||
data=transmitters_data, many=True, allow_empty=True
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
else:
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class LatestTleSetViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
|
||||
"""
|
||||
Read-only view into the most recent two-line elements (TLE) in the SatNOGS DB
|
||||
database
|
||||
"""
|
||||
renderer_classes = [JSONRenderer, BrowsableAPIRenderer]
|
||||
queryset = LatestTleSet.objects.all().select_related('satellite').exclude(
|
||||
latest_distributable__isnull=True
|
||||
).annotate(
|
||||
tle0=F('latest_distributable__tle0'),
|
||||
tle1=F('latest_distributable__tle1'),
|
||||
tle2=F('latest_distributable__tle2'),
|
||||
tle_source=F('latest_distributable__tle_source'),
|
||||
updated=F('latest_distributable__updated')
|
||||
)
|
||||
serializer_class = serializers.LatestTleSetSerializer
|
||||
filterset_class = filters.LatestTleSetViewFilter
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Returns latest TLE queryset depending on user permissions
|
||||
"""
|
||||
if self.request.user.has_perm('base.access_all_tles'):
|
||||
return LatestTleSet.objects.all().select_related('satellite').exclude(
|
||||
latest__isnull=True
|
||||
).annotate(
|
||||
tle0=F('latest__tle0'),
|
||||
tle1=F('latest__tle1'),
|
||||
tle2=F('latest__tle2'),
|
||||
tle_source=F('latest__tle_source'),
|
||||
updated=F('latest__updated')
|
||||
)
|
||||
return self.queryset
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name='app_source',
|
||||
description='The submission source for the telemetry frames: manual (a manual \
|
||||
upload/entry), network (SatNOGS Network observations), or sids \
|
||||
(legacy API submission)',
|
||||
),
|
||||
OpenApiParameter(
|
||||
name='observer',
|
||||
description='(string) name of the observer (submitter) to retrieve telemetry data \
|
||||
from'
|
||||
),
|
||||
OpenApiParameter(
|
||||
name='satellite',
|
||||
description='NORAD ID of a satellite to filter telemetry data for',
|
||||
examples=[ISS_EXAMPLE],
|
||||
),
|
||||
OpenApiParameter(name='transmitter', description='Not currently in use'),
|
||||
],
|
||||
),
|
||||
)
|
||||
class TelemetryViewSet( # pylint: disable=R0901,R0912,R0915
|
||||
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
|
||||
viewsets.GenericViewSet):
|
||||
"""
|
||||
View into the Telemetry objects in the SatNOGS DB database. Currently,
|
||||
this table is inclusive of all data collected from satellite downlink
|
||||
observations
|
||||
"""
|
||||
renderer_classes = [
|
||||
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
|
||||
]
|
||||
queryset = DemodData.objects.all().select_related('satellite', 'transmitter')
|
||||
serializer_class = serializers.TelemetrySerializer
|
||||
filter_class = filters.TelemetryViewFilter
|
||||
permission_classes = (AllowAny, )
|
||||
parser_classes = (FormParser, FileUploadParser)
|
||||
filterset_class = filters.TelemetryViewFilter
|
||||
permission_classes = [SafeMethodsWithPermission]
|
||||
throttle_classes = [
|
||||
GetTelemetryAnononymousRateThrottle, GetTelemetryUserRateThrottle,
|
||||
GetTelemetryViolatorThrottle
|
||||
]
|
||||
parser_classes = (FormParser, MultiPartParser, FileUploadParser)
|
||||
pagination_class = pagination.LinkedHeaderPageNumberPagination
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""
|
||||
Lists data from satellite if they are filtered by NORAD ID or Satellite ID. Also logs the
|
||||
requests if it is set to do so.
|
||||
"""
|
||||
satellite = request.query_params.get('satellite', None)
|
||||
sat_id = request.query_params.get('sat_id', None)
|
||||
|
||||
if not (satellite or sat_id):
|
||||
data = {
|
||||
'detail': (
|
||||
'For getting data please use either satellite(NORAD ID) filter or'
|
||||
'sat_id(Satellite ID) filter'
|
||||
),
|
||||
'results': None
|
||||
}
|
||||
response = Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
response.exception = True
|
||||
return response
|
||||
|
||||
if settings.LOG_TELEMETRY_REQUESTS:
|
||||
user_id = str(request.user.id)
|
||||
remote_address = str(request.META.get("REMOTE_ADDR"))
|
||||
x_forwarded_for = str(request.META.get("HTTP_X_FORWARDED_FOR"))
|
||||
timestamp = now().isoformat()
|
||||
request_data = user_id + ';' + remote_address + ';' + x_forwarded_for + ';' + timestamp
|
||||
cache.set(
|
||||
'telemetry_log_' + user_id + '_' + timestamp, request_data,
|
||||
settings.TELEMETRY_LOGS_TIME_TO_LIVE
|
||||
)
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
@extend_schema(
|
||||
responses={'201': None}, # None
|
||||
)
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""
|
||||
Creates a frame of telemetry data from a satellite observation.
|
||||
"""
|
||||
# pylint: disable=R0914
|
||||
data = {}
|
||||
|
||||
norad_cat_id = request.data.get('noradID')
|
||||
norad_id = request.data.get('noradID')
|
||||
|
||||
if not Satellite.objects.filter(norad_cat_id=norad_cat_id).exists():
|
||||
try:
|
||||
update_satellite(norad_cat_id, update_name=True, update_tle=True)
|
||||
except LookupError:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
if norad_id:
|
||||
satellite = Satellite.objects.get(satellite_entry__norad_cat_id=norad_id)
|
||||
else:
|
||||
raise ValueError
|
||||
except Satellite.DoesNotExist:
|
||||
satellite_identifier = SatelliteIdentifier.objects.create()
|
||||
satellite_entry = SatelliteEntry.objects.create(
|
||||
norad_cat_id=norad_id,
|
||||
name='New Satellite',
|
||||
satellite_identifier=satellite_identifier,
|
||||
created=now()
|
||||
)
|
||||
satellite = Satellite.objects.create(
|
||||
satellite_identifier=satellite_identifier, satellite_entry=satellite_entry
|
||||
)
|
||||
update_satellite_name.delay(int(norad_id))
|
||||
except ValueError:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
data['satellite'] = Satellite.objects.get(norad_cat_id=norad_cat_id).id
|
||||
data['satellite'] = satellite.id
|
||||
data['station'] = request.data.get('source')
|
||||
timestamp = request.data.get('timestamp')
|
||||
data['timestamp'] = timestamp
|
||||
data['timestamp'] = request.data.get('timestamp')
|
||||
if request.data.get('version'):
|
||||
data['version'] = request.data.get('version')
|
||||
observation_id = ''
|
||||
if request.data.get('observation_id'):
|
||||
observation_id = request.data.get('observation_id')
|
||||
data['observation_id'] = observation_id
|
||||
station_id = ''
|
||||
if request.data.get('station_id'):
|
||||
station_id = request.data.get('station_id')
|
||||
data['station_id'] = station_id
|
||||
|
||||
# Convert coordinates to omit N-S and W-E designators
|
||||
lat = request.data.get('latitude')
|
||||
lng = request.data.get('longitude')
|
||||
if any(x.isalpha() for x in lat):
|
||||
data['lat'] = (-float(lat[:-1]) if ('S' in lat) else float(lat[:-1]))
|
||||
else:
|
||||
data['lat'] = float(lat)
|
||||
if any(x.isalpha() for x in lng):
|
||||
data['lng'] = (-float(lng[:-1]) if ('W' in lng) else float(lng[:-1]))
|
||||
else:
|
||||
data['lng'] = float(lng)
|
||||
try:
|
||||
if any(x.isalpha() for x in lat):
|
||||
data['lat'] = (-float(lat[:-1]) if ('S' in lat) else float(lat[:-1]))
|
||||
else:
|
||||
data['lat'] = float(lat)
|
||||
if any(x.isalpha() for x in lng):
|
||||
data['lng'] = (-float(lng[:-1]) if ('W' in lng) else float(lng[:-1]))
|
||||
else:
|
||||
data['lng'] = float(lng)
|
||||
except ValueError:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Network or SiDS submission?
|
||||
if request.data.get('satnogs_network'):
|
||||
data['source'] = 'network'
|
||||
data['app_source'] = 'network'
|
||||
else:
|
||||
data['source'] = 'sids'
|
||||
data['app_source'] = 'sids'
|
||||
|
||||
# Create file out of frame string
|
||||
frame = ContentFile(request.data.get('frame'), name='sids')
|
||||
data['payload_frame'] = frame
|
||||
# Create observer
|
||||
qth = gridsquare(data['lat'], data['lng'])
|
||||
observer = '{0}-{1}'.format(data['station'], qth)
|
||||
data['observer'] = observer
|
||||
|
||||
serializer = serializers.SidsSerializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
||||
# Run task to decode the current frame
|
||||
decode_current_frame.delay(satellite.satellite_identifier.sat_id, serializer.instance.pk)
|
||||
|
||||
# Run task to publish the current frame via ZeroMQ
|
||||
if settings.ZEROMQ_ENABLE:
|
||||
publish_current_frame.delay(
|
||||
request.data.get('timestamp'), request.data.get('frame'), observer, {
|
||||
'norad_id': norad_id,
|
||||
'observation_id': observation_id,
|
||||
'station_id': station_id
|
||||
}
|
||||
)
|
||||
|
||||
return Response(status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
'network_obs_id',
|
||||
OpenApiTypes.INT64,
|
||||
required=False,
|
||||
description='Given a SatNOGS Network observation ID, this will return any \
|
||||
artifacts files associated with the observation.'
|
||||
),
|
||||
],
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
'id',
|
||||
OpenApiTypes.URI,
|
||||
OpenApiParameter.PATH,
|
||||
description='The ID for the requested artifact entry in DB'
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
class ArtifactViewSet( # pylint: disable=R0901
|
||||
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
|
||||
viewsets.GenericViewSet):
|
||||
"""
|
||||
Artifacts are file-formatted objects collected from a satellite observation.
|
||||
"""
|
||||
queryset = Artifact.objects.all()
|
||||
filterset_class = filters.ArtifactViewFilter
|
||||
permission_classes = [IsAuthenticatedOrOptions]
|
||||
parser_classes = (FormParser, MultiPartParser)
|
||||
pagination_class = pagination.LinkedHeaderPageNumberPagination
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""Returns the right serializer depending on http method that is used"""
|
||||
if self.action == 'create':
|
||||
return serializers.NewArtifactSerializer
|
||||
return serializers.ArtifactSerializer
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""
|
||||
Creates observation artifact from an [HDF5 formatted file][hdf5ref]
|
||||
* Requires session or key authentication to create an artifact
|
||||
|
||||
[hdf5ref]: https://en.wikipedia.org/wiki/Hierarchical_Data_Format
|
||||
"""
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
try:
|
||||
if serializer.is_valid():
|
||||
data = serializer.save()
|
||||
http_response = {}
|
||||
http_response['id'] = data.id
|
||||
response = Response(http_response, status=status.HTTP_200_OK)
|
||||
else:
|
||||
data = serializer.errors
|
||||
response = Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
except (ValidationError, ValueError, OSError) as error:
|
||||
response = Response(str(error), status=status.HTTP_400_BAD_REQUEST)
|
||||
return response
|
||||
|
|
617
db/base/admin.py
617
db/base/admin.py
|
@ -1,162 +1,565 @@
|
|||
import logging
|
||||
"""Defines functions and settings for the django admin interface"""
|
||||
from socket import error as socket_error
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.contrib import admin, messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template.loader import render_to_string
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import re_path, reverse
|
||||
from django.utils.timezone import now
|
||||
|
||||
from db.base.models import Mode, Satellite, Transmitter, Suggestion, DemodData, Telemetry
|
||||
from db.base.tasks import check_celery, reset_decoded_data, decode_all_data
|
||||
|
||||
|
||||
logger = logging.getLogger('db')
|
||||
from db.base.models import Artifact, DemodData, ExportedFrameset, LatestTleSet, Mode, Operator, \
|
||||
Satellite, SatelliteEntry, SatelliteIdentifier, SatelliteSuggestion, Telemetry, Tle, \
|
||||
Transmitter, TransmitterEntry, TransmitterSuggestion
|
||||
from db.base.tasks import check_celery, decode_all_data, update_tle_sets
|
||||
from db.base.utils import update_latest_tle_sets
|
||||
|
||||
|
||||
@admin.register(Mode)
|
||||
class ModeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
"""Defines Mode view in django admin UI"""
|
||||
list_display = ('name', )
|
||||
|
||||
|
||||
@admin.register(Operator)
|
||||
class OperatorAdmin(admin.ModelAdmin):
|
||||
"""Defines Operator view in django admin UI"""
|
||||
list_display = ('name', 'names', 'website')
|
||||
search_fields = ('name', 'names')
|
||||
|
||||
|
||||
@admin.register(SatelliteIdentifier)
|
||||
class SatelliteIdentifierAdmin(admin.ModelAdmin):
|
||||
"""Defines SatelliteIdentifier view in django admin UI"""
|
||||
list_display = ('id', 'sat_id', 'created')
|
||||
search_fields = ('sat_id', )
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def get_actions(self, request):
|
||||
actions = super().get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
|
||||
@admin.register(SatelliteEntry)
|
||||
class SatelliteEntryAdmin(admin.ModelAdmin):
|
||||
"""Defines Satellite Entry view in django admin UI"""
|
||||
list_display = (
|
||||
'id', 'satellite_identifier', 'name', 'norad_cat_id', 'status', 'decayed',
|
||||
'norad_follow_id', 'citation', 'approved', 'created', 'created_by', 'reviewed', 'reviewer'
|
||||
)
|
||||
search_fields = ('name', 'norad_cat_id', 'norad_follow_id', 'satellite_identifier__sat_id')
|
||||
list_filter = ('status', 'decayed', 'reviewed', 'approved', 'satellite_identifier__sat_id')
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).select_related('satellite_identifier')
|
||||
|
||||
# workaround for readonly CountryField, more at:
|
||||
# https://github.com/SmileyChris/django-countries/issues/298
|
||||
def get_fields(self, request, obj=None):
|
||||
fields = super().get_fields(request, obj)
|
||||
if not self.has_change_permission(request):
|
||||
try:
|
||||
index = fields.index('countries')
|
||||
fields[index] = 'countries_str'
|
||||
except ValueError:
|
||||
pass
|
||||
return fields
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
if request.user.has_perm('base.delete_satelliteentry'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_actions(self, request):
|
||||
actions = super().get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
|
||||
@admin.register(SatelliteSuggestion)
|
||||
class SatelliteSuggestionAdmin(admin.ModelAdmin):
|
||||
"""Defines SatelliteSuggestion view in django admin UI"""
|
||||
list_display = (
|
||||
'id', 'satellite_identifier', 'name', 'norad_cat_id', 'citation', 'created', 'created_by'
|
||||
)
|
||||
search_fields = ('name', 'norad_cat_id', 'norad_follow_id', 'satellite_identifier__sat_id')
|
||||
list_filter = ('satellite_identifier', )
|
||||
actions = ['approve_suggestion', 'reject_suggestion']
|
||||
|
||||
# workaround for readonly CountryField, more at:
|
||||
# https://github.com/SmileyChris/django-countries/issues/298
|
||||
def get_fields(self, request, obj=None):
|
||||
fields = super().get_fields(request, obj)
|
||||
if not self.has_change_permission(request):
|
||||
try:
|
||||
index = fields.index('countries')
|
||||
fields[index] = 'countries_str'
|
||||
except ValueError:
|
||||
pass
|
||||
return fields
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def get_actions(self, request):
|
||||
"""Returns the actions a user can take on a SatelliteSuggestion
|
||||
|
||||
For example, delete, approve, or reject
|
||||
|
||||
:returns: list of actions the user can take on SatelliteSuggestion
|
||||
"""
|
||||
actions = super().get_actions(request)
|
||||
if not request.user.has_perm('base.delete_satellitesuggestion'):
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
def approve_suggestion(self, request, queryset):
|
||||
"""Returns the SatelliteSuggestion page after approving suggestions
|
||||
|
||||
:param queryset: the SatelliteSuggestion entries to be approved
|
||||
:returns: SatelliteSuggestion admin page
|
||||
"""
|
||||
queryset_size = len(queryset)
|
||||
for entry in queryset:
|
||||
satellite = Satellite.objects.get(satellite_identifier=entry.satellite_identifier)
|
||||
entry.approved = True
|
||||
entry.reviewed = now()
|
||||
entry.reviewer = request.user
|
||||
entry.save()
|
||||
satellite.satellite_entry = entry
|
||||
satellite.save()
|
||||
if queryset_size == 1:
|
||||
self.message_user(request, "Satellite suggestion was successfully approved")
|
||||
else:
|
||||
self.message_user(request, "Satellite suggestions were successfully approved")
|
||||
|
||||
approve_suggestion.short_description = 'Approve selected satellite suggestions'
|
||||
|
||||
def reject_suggestion(self, request, queryset):
|
||||
"""Returns the SatelliteSuggestion page after rejecting suggestions
|
||||
|
||||
:param queryset: the SatelliteSuggestion entries to be rejected
|
||||
:returns: SatelliteSuggestion admin page
|
||||
"""
|
||||
queryset_size = len(queryset)
|
||||
for entry in queryset:
|
||||
entry.approved = False
|
||||
entry.reviewed = now()
|
||||
entry.reviewer = request.user
|
||||
entry.save()
|
||||
if queryset_size == 1:
|
||||
self.message_user(request, "Satellite suggestion was successfully rejected")
|
||||
else:
|
||||
self.message_user(request, "Satellite suggestions were successfully rejected")
|
||||
|
||||
reject_suggestion.short_description = 'Reject selected satellite suggestions'
|
||||
|
||||
|
||||
@admin.register(Satellite)
|
||||
class SatelliteAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'norad_cat_id', 'status', 'decayed')
|
||||
search_fields = ('name', 'norad_cat_id')
|
||||
list_filter = ('status', 'decayed')
|
||||
"""Defines Satellite view in django admin UI"""
|
||||
list_display = (
|
||||
'id', 'sat_id', 'associated_satellite', 'last_modified', 'satellite_entry_pk',
|
||||
'norad_cat_id', 'name', 'norad_follow_id', 'status', 'decayed'
|
||||
)
|
||||
search_fields = (
|
||||
'satellite_identifier__sat_id', 'satellite_entry__name', 'satellite_entry__norad_cat_id',
|
||||
'satellite_entry__norad_follow_id'
|
||||
)
|
||||
list_filter = ('satellite_entry__status', 'satellite_entry__decayed', 'associated_satellite')
|
||||
|
||||
def get_urls(self):
|
||||
urls = super(SatelliteAdmin, self).get_urls()
|
||||
"""Returns django urls for the Satellite view
|
||||
|
||||
check_celery -- url for the check_celery function
|
||||
decode_all_data -- url for the decode_all_data function
|
||||
|
||||
:returns: Django urls for the Satellite admin view
|
||||
"""
|
||||
urls = super().get_urls()
|
||||
my_urls = [
|
||||
url(r'^check_celery/$', self.check_celery, name='check_celery'),
|
||||
url(r'^reset_data/(?P<norad>[0-9]+)/$', self.reset_data,
|
||||
name='reset_data'),
|
||||
url(r'^decode_all_data/(?P<norad>[0-9]+)/$', self.decode_all_data,
|
||||
name='decode_all_data'),
|
||||
re_path(r'^check_celery/$', self.check_celery, name='check_celery'),
|
||||
re_path(
|
||||
r'^decode_all_data/(?P<sat_id>[A-Z]{4,4}(?:-\d\d\d\d){4,4})/$',
|
||||
self.decode_all_data,
|
||||
name='decode_all_data'
|
||||
)
|
||||
]
|
||||
return my_urls + urls
|
||||
|
||||
def check_celery(self, request):
|
||||
def sat_id(self, obj): # pylint: disable=R0201
|
||||
"""Return the Satellite Identifier for that satellite"""
|
||||
return obj.satellite_identifier.sat_id
|
||||
|
||||
def satellite_entry_pk(self, obj): # pylint: disable=R0201
|
||||
"""Return the pk of the Satellite Entry object for that satellite"""
|
||||
if obj.satellite_entry:
|
||||
return obj.satellite_entry.pk
|
||||
return None
|
||||
|
||||
def norad_cat_id(self, obj): # pylint: disable=R0201
|
||||
"""Return the satellite NORAD ID"""
|
||||
if obj.satellite_entry:
|
||||
return obj.satellite_entry.norad_cat_id
|
||||
return None
|
||||
|
||||
def norad_follow_id(self, obj): # pylint: disable=R0201
|
||||
"""Return the NORAD ID that satellite follows"""
|
||||
if obj.satellite_entry:
|
||||
return obj.satellite_entry.norad_follow_id
|
||||
return None
|
||||
|
||||
def name(self, obj): # pylint: disable=R0201
|
||||
"""Return the satellite name"""
|
||||
if obj.satellite_entry:
|
||||
return obj.satellite_entry.name
|
||||
return None
|
||||
|
||||
def status(self, obj): # pylint: disable=R0201
|
||||
"""Return the satellite status"""
|
||||
if obj.satellite_entry:
|
||||
return obj.satellite_entry.status
|
||||
return None
|
||||
|
||||
def decayed(self, obj): # pylint: disable=R0201
|
||||
"""Return the dacayed date of the satellite"""
|
||||
if obj.satellite_entry:
|
||||
return obj.satellite_entry.decayed
|
||||
return None
|
||||
|
||||
def check_celery(self, request): # pylint: disable=R0201
|
||||
"""Returns status of Celery workers
|
||||
|
||||
Check the delay for celery workers, return an error if a connection
|
||||
can not be made or if the delay is too long. Otherwise return that
|
||||
Celery is OK.
|
||||
|
||||
:returns: admin home page redirect with popup message
|
||||
"""
|
||||
try:
|
||||
investigator = check_celery.delay()
|
||||
except socket_error as e:
|
||||
messages.error(request, 'Cannot connect to broker: %s' % e)
|
||||
except socket_error as error:
|
||||
messages.error(request, 'Cannot connect to broker: %s' % error)
|
||||
return HttpResponseRedirect(reverse('admin:index'))
|
||||
|
||||
try:
|
||||
investigator.get(timeout=5)
|
||||
except investigator.TimeoutError as e:
|
||||
messages.error(request, 'Worker timeout: %s' % e)
|
||||
except investigator.TimeoutError as error:
|
||||
messages.error(request, 'Worker timeout: %s' % error)
|
||||
else:
|
||||
messages.success(request, 'Celery is OK')
|
||||
finally:
|
||||
return HttpResponseRedirect(reverse('admin:index'))
|
||||
|
||||
# resets all decoded data and changes the is_decoded flag back to False
|
||||
# THIS IS VERY DISTRUCTIVE, but the expectation is that a decode_all_data
|
||||
# would follow.
|
||||
def reset_data(self, request, norad):
|
||||
reset_decoded_data.delay(norad)
|
||||
messages.success(request, 'Data reset task was triggered successfully!')
|
||||
return redirect(reverse('admin:index'))
|
||||
return HttpResponseRedirect(reverse('admin:index'))
|
||||
|
||||
# force a decode of all data for a norad ID. This could be very resource
|
||||
# intensive but necessary when catching a satellite up with a new decoder
|
||||
def decode_all_data(self, request, norad):
|
||||
decode_all_data.delay(norad)
|
||||
def decode_all_data(self, request, sat_id): # pylint: disable=R0201
|
||||
"""Returns the admin home page, while triggering a Celery decode task
|
||||
|
||||
Forces a decode of all data for a Satellite Identifier. This could be very resource
|
||||
intensive but necessary when catching a satellite up with a new decoder
|
||||
|
||||
:param sat_id: the Satellite Identifier for the satellite to decode
|
||||
:returns: Admin home page
|
||||
"""
|
||||
satellite = Satellite.objects.get(satellite_identifier__sat_id=sat_id)
|
||||
|
||||
# Allow decoding data only for Satellites that are not merged and
|
||||
# suggest user trigger decoding for the associated_satellite which will
|
||||
# include all DemodData of the satellites that are associated with it
|
||||
if satellite.associated_satellite:
|
||||
messages.error(
|
||||
request,
|
||||
'Satellite has been merged, for decoding data trigger "Decode All Data" for "%s"'
|
||||
% satellite.associated_satellite
|
||||
)
|
||||
return redirect(reverse('admin:index'))
|
||||
decode_all_data.delay(sat_id)
|
||||
messages.success(request, 'Decode task was triggered successfully!')
|
||||
return redirect(reverse('admin:index'))
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def get_actions(self, request):
|
||||
actions = super().get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
|
||||
@admin.register(TransmitterEntry)
|
||||
class TransmitterEntryAdmin(admin.ModelAdmin):
|
||||
"""Defines TransmitterEntry view in django admin UI"""
|
||||
list_display = (
|
||||
'id', 'uuid', 'description', 'satellite', 'service', 'type', 'downlink_mode',
|
||||
'uplink_mode', 'baud', 'downlink_low', 'downlink_high', 'downlink_drift', 'uplink_low',
|
||||
'uplink_high', 'uplink_drift', 'citation', 'approved', 'status', 'created', 'created_by',
|
||||
'reviewed', 'reviewer'
|
||||
)
|
||||
search_fields = (
|
||||
'uuid', 'satellite__satellite_identifier__sat_id', 'satellite__satellite_entry__name',
|
||||
'satellite__satellite_entry__norad_cat_id'
|
||||
)
|
||||
list_filter = (
|
||||
'reviewed',
|
||||
'approved',
|
||||
'type',
|
||||
'status',
|
||||
'service',
|
||||
'downlink_mode',
|
||||
'uplink_mode',
|
||||
'baud',
|
||||
)
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
if request.user.has_perm('base.delete_transmitterentry'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_actions(self, request):
|
||||
actions = super().get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
|
||||
@admin.register(TransmitterSuggestion)
|
||||
class TransmitterSuggestionAdmin(admin.ModelAdmin):
|
||||
"""Defines TransmitterSuggestion view in django admin UI"""
|
||||
list_display = (
|
||||
'uuid', 'description', 'satellite', 'service', 'type', 'downlink_mode', 'uplink_mode',
|
||||
'baud', 'downlink_low', 'downlink_high', 'downlink_drift', 'uplink_low', 'uplink_high',
|
||||
'uplink_drift', 'citation', 'status', 'created', 'created_by'
|
||||
)
|
||||
search_fields = (
|
||||
'uuid', 'satellite__satellite_identifier__sat_id', 'satellite__satellite_entry__name',
|
||||
'satellite__satellite_entry__norad_cat_id'
|
||||
)
|
||||
list_filter = (
|
||||
'type',
|
||||
'downlink_mode',
|
||||
'uplink_mode',
|
||||
'baud',
|
||||
'service',
|
||||
)
|
||||
actions = ['approve_suggestion', 'reject_suggestion']
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def get_actions(self, request):
|
||||
"""Returns the actions a user can take on a TransmitterSuggestion
|
||||
|
||||
For example, delete, approve, or reject
|
||||
|
||||
:returns: list of actions the user can take on TransmitterSuggestion
|
||||
"""
|
||||
actions = super().get_actions(request)
|
||||
if not request.user.has_perm('base.delete_transmittersuggestion'):
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
def approve_suggestion(self, request, queryset):
|
||||
"""Returns the TransmitterSuggestion page after approving suggestions
|
||||
|
||||
:param queryset: the TransmitterSuggestion entries to be approved
|
||||
:returns: TransmitterSuggestion admin page
|
||||
"""
|
||||
queryset_size = len(queryset)
|
||||
for entry in queryset:
|
||||
entry.approved = True
|
||||
entry.reviewed = now()
|
||||
entry.reviewer = request.user
|
||||
entry.save()
|
||||
if queryset_size == 1:
|
||||
self.message_user(request, "Transmitter suggestion was successfully approved")
|
||||
else:
|
||||
self.message_user(request, "Transmitter suggestions were successfully approved")
|
||||
|
||||
approve_suggestion.short_description = 'Approve selected transmitter suggestions'
|
||||
|
||||
def reject_suggestion(self, request, queryset):
|
||||
"""Returns the TransmitterSuggestion page after rejecting suggestions
|
||||
|
||||
:param queryset: the TransmitterSuggestion entries to be rejected
|
||||
:returns: TransmitterSuggestion admin page
|
||||
"""
|
||||
queryset_size = len(queryset)
|
||||
for entry in queryset:
|
||||
entry.approved = False
|
||||
entry.reviewed = now()
|
||||
entry.reviewer = request.user
|
||||
entry.save()
|
||||
if queryset_size == 1:
|
||||
self.message_user(request, "Transmitter suggestion was successfully rejected")
|
||||
else:
|
||||
self.message_user(request, "Transmitter suggestions were successfully rejected")
|
||||
|
||||
reject_suggestion.short_description = 'Reject selected transmitter suggestions'
|
||||
|
||||
|
||||
@admin.register(Transmitter)
|
||||
class TransmitterAdmin(admin.ModelAdmin):
|
||||
list_display = ('uuid', 'description', 'satellite', 'type', 'alive', 'mode', 'uplink_low',
|
||||
'uplink_high', 'uplink_drift', 'downlink_low', 'downlink_high',
|
||||
'downlink_drift', 'baud',)
|
||||
search_fields = ('satellite__id', 'uuid', 'satellite__name', 'satellite__norad_cat_id')
|
||||
list_filter = ('type', 'alive', 'mode', 'baud',)
|
||||
readonly_fields = ('uuid', 'satellite', 'approved',)
|
||||
"""Defines Transmitter view in django admin UI"""
|
||||
list_display = (
|
||||
'id', 'uuid', 'description', 'satellite', 'service', 'type', 'downlink_mode',
|
||||
'uplink_mode', 'baud', 'downlink_low', 'downlink_high', 'downlink_drift', 'uplink_low',
|
||||
'uplink_high', 'uplink_drift', 'citation', 'status', 'created', 'created_by', 'reviewed',
|
||||
'reviewer'
|
||||
)
|
||||
search_fields = (
|
||||
'uuid', 'satellite__satellite_identifier__sat_id', 'satellite__satellite_entry__name',
|
||||
'satellite__satellite_entry__norad_cat_id'
|
||||
)
|
||||
list_filter = (
|
||||
'type',
|
||||
'status',
|
||||
'service',
|
||||
'downlink_mode',
|
||||
'uplink_mode',
|
||||
'baud',
|
||||
)
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def get_actions(self, request):
|
||||
actions = super().get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
|
||||
@admin.register(Suggestion)
|
||||
class SuggestionAdmin(admin.ModelAdmin):
|
||||
list_display = ('uuid', 'description', 'transmitter_uuid', 'user', 'type', 'satellite',
|
||||
'uplink_low', 'uplink_high', 'uplink_drift', 'downlink_low', 'downlink_high',
|
||||
'downlink_drift',)
|
||||
search_fields = ('satellite', 'uuid',)
|
||||
list_filter = ('type', 'mode',)
|
||||
readonly_fields = ('uuid', 'satellite', 'transmitter', 'approved', 'user',
|
||||
'citation', 'transmitter_data')
|
||||
actions = ['approve_suggestion']
|
||||
@admin.register(Tle)
|
||||
class TleAdmin(admin.ModelAdmin):
|
||||
"""Define TLE view in django admin UI"""
|
||||
list_display = ('satellite_name', 'tle0', 'tle1', 'updated', 'tle_source')
|
||||
list_filter = ('tle_source', 'satellite__satellite_entry__name')
|
||||
|
||||
def approve_suggestion(self, request, queryset):
|
||||
for obj in queryset:
|
||||
try:
|
||||
transmitter = Transmitter.objects.get(id=obj.transmitter.id)
|
||||
transmitter.update_from_suggestion(obj)
|
||||
obj.delete()
|
||||
except (Transmitter.DoesNotExist, AttributeError):
|
||||
obj.approved = True
|
||||
obj.citation = ''
|
||||
obj.user = None
|
||||
obj.save()
|
||||
def satellite_name(self, obj): # pylint: disable=no-self-use
|
||||
"""Return the satellite name"""
|
||||
return obj.satellite.satellite_entry.name
|
||||
|
||||
# Notify user
|
||||
current_site = get_current_site(request)
|
||||
subject = '[{0}] Your suggestion was approved'.format(current_site.name)
|
||||
template = 'emails/suggestion_approved.txt'
|
||||
data = {
|
||||
'satname': obj.satellite.name
|
||||
}
|
||||
message = render_to_string(template, {'data': data})
|
||||
try:
|
||||
obj.user.email_user(subject, message, from_email=settings.DEFAULT_FROM_EMAIL)
|
||||
except Exception:
|
||||
logger.error(
|
||||
'Could not send email to user',
|
||||
exc_info=True
|
||||
)
|
||||
def get_urls(self):
|
||||
"""Returns django urls for Tle view
|
||||
|
||||
rows_updated = queryset.count()
|
||||
update_tle_sets -- url for the update_tle_sets function
|
||||
|
||||
# Print a message
|
||||
if rows_updated == 1:
|
||||
message_bit = '1 suggestion was'
|
||||
else:
|
||||
message_bit = '{0} suggestions were'.format(rows_updated)
|
||||
self.message_user(request, '{0} successfully approved.'.format(message_bit))
|
||||
:returns: Django urls for the Tle admin view
|
||||
"""
|
||||
urls = super().get_urls()
|
||||
my_urls = [
|
||||
re_path(r'^update_tle_sets/$', self.update_tle_sets, name='update_tle_sets'),
|
||||
]
|
||||
return my_urls + urls
|
||||
|
||||
approve_suggestion.short_description = 'Approve selected suggestions'
|
||||
def update_tle_sets(self, request): # pylint: disable=R0201
|
||||
"""Returns the admin home page, while triggering a Celery update tle sets task
|
||||
|
||||
def transmitter_uuid(self, obj):
|
||||
try:
|
||||
return obj.transmitter.uuid
|
||||
except Exception:
|
||||
return '-'
|
||||
:returns: Admin home page
|
||||
"""
|
||||
update_tle_sets.delay()
|
||||
messages.success(request, 'Update TLE sets task was triggered successfully!')
|
||||
return redirect(reverse('admin:index'))
|
||||
|
||||
def transmitter_data(self, obj):
|
||||
if obj.transmitter:
|
||||
redirect_url = reverse('admin:base_transmitter_changelist')
|
||||
extra = '{0}'.format(obj.transmitter.pk)
|
||||
return '<a href="{0}">Transmitter Initial Data</a>'.format(
|
||||
redirect_url + extra)
|
||||
else:
|
||||
return '-'
|
||||
transmitter_data.allow_tags = True
|
||||
def save_model(self, request, obj, form, change):
|
||||
super().save_model(request, obj, form, change)
|
||||
update_latest_tle_sets(satellite_pks=[obj.satellite.pk])
|
||||
|
||||
def delete_model(self, request, obj):
|
||||
super().delete_model(request, obj)
|
||||
update_latest_tle_sets(satellite_pks=[obj.satellite.pk])
|
||||
|
||||
def delete_queryset(self, request, queryset):
|
||||
satellites = [tle.satellite.pk for tle in queryset]
|
||||
super().delete_queryset(request, queryset)
|
||||
update_latest_tle_sets(satellite_pks=satellites)
|
||||
|
||||
|
||||
@admin.register(LatestTleSet)
|
||||
class LatestTleSetAdmin(admin.ModelAdmin):
|
||||
"""Defines LatestTleSet view in django admin UI"""
|
||||
list_display = ('satellite', 'latest', 'latest_distributable', 'last_modified')
|
||||
search_fields = (
|
||||
'satellite__satellite_identifier__sat_id', 'satellite__satellite_entry__norad_cat_id',
|
||||
'satellite__satellite_entry__name'
|
||||
)
|
||||
|
||||
|
||||
@admin.register(Telemetry)
|
||||
class TelemetryAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'decoder')
|
||||
"""Defines Telemetry view in django admin UI"""
|
||||
list_display = ('name', 'decoder', 'satellite')
|
||||
search_fields = (
|
||||
'satellite__satellite_identifier__sat_id', 'satellite__satellite_entry__norad_cat_id',
|
||||
'satellite__satellite_entry__name'
|
||||
)
|
||||
|
||||
|
||||
@admin.register(DemodData)
|
||||
class DemodDataAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'satellite', 'source', 'observer')
|
||||
search_fields = ('transmitter__uuid', 'satellite__norad_cat_id', 'observer')
|
||||
"""Defines DemodData view in django admin UI"""
|
||||
list_display = ('id', 'satellite', 'app_source', 'observer', 'observation_id', 'station_id')
|
||||
search_fields = (
|
||||
'transmitter__uuid', 'satellite__satellite_identifier__sat_id',
|
||||
'satellite__satellite_entry__norad_cat_id', 'observer', 'observation_id', 'station_id'
|
||||
)
|
||||
list_filter = (
|
||||
'satellite',
|
||||
'app_source',
|
||||
'observer',
|
||||
)
|
||||
|
||||
def satellite(self, obj):
|
||||
def satellite(self, obj): # pylint: disable=R0201
|
||||
"""Returns the Satellite object associated with this DemodData
|
||||
|
||||
:param obj: DemodData object
|
||||
:returns: Satellite object
|
||||
"""
|
||||
return obj.satellite
|
||||
|
||||
|
||||
@admin.register(ExportedFrameset)
|
||||
class ExportedFramesetAdmin(admin.ModelAdmin):
|
||||
"""Defines ExportedFrameset view in django admin UI"""
|
||||
list_display = ('id', 'created', 'user', 'satellite', 'exported_file', 'start', 'end')
|
||||
search_fields = ('user', 'satellite__satellite_entry__norad_cat_id')
|
||||
list_filter = ('satellite', 'user')
|
||||
|
||||
|
||||
@admin.register(Artifact)
|
||||
class ArtifactAdmin(admin.ModelAdmin):
|
||||
"""Defines Artifact view in django admin UI"""
|
||||
list_display = ('id', 'network_obs_id', 'artifact_file')
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
"""SatNOGS DB Base app config"""
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BaseConfig(AppConfig):
|
||||
"""Set configuration of the SatNOGS DB Base app"""
|
||||
name = 'db.base'
|
||||
verbose_name = "Base"
|
||||
|
||||
def ready(self):
|
||||
from db.base import signals # noqa: F401; pylint: disable=C0415,W0611
|
|
@ -1,34 +1,61 @@
|
|||
"""SatNOGS DB django context processors"""
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from satnogsdecoders import __version__ as satnogsdecoders_version
|
||||
|
||||
from db import __version__
|
||||
|
||||
|
||||
def analytics(request):
|
||||
"""Returns analytics code."""
|
||||
if settings.ENVIRONMENT == 'production':
|
||||
return {'analytics_code': render_to_string('includes/analytics.html')}
|
||||
rendered_string = {'analytics_code': render_to_string('includes/analytics.html')}
|
||||
else:
|
||||
return {'analytics_code': ''}
|
||||
rendered_string = {'analytics_code': ''}
|
||||
return rendered_string
|
||||
|
||||
|
||||
def stage_notice(request):
|
||||
"""Displays stage notice."""
|
||||
if settings.ENVIRONMENT == 'stage':
|
||||
return {'stage_notice': render_to_string('includes/stage_notice.html')}
|
||||
rendered_string = {'stage_notice': render_to_string('includes/stage_notice.html')}
|
||||
else:
|
||||
return {'stage_notice': ''}
|
||||
rendered_string = {'stage_notice': ''}
|
||||
return rendered_string
|
||||
|
||||
|
||||
def auth_block(request):
|
||||
"""Displays auth links local vs auth0."""
|
||||
if settings.AUTH0:
|
||||
return {'auth_block': render_to_string('includes/auth_auth0.html')}
|
||||
rendered_string = {'auth_block': render_to_string('includes/auth_auth0.html')}
|
||||
else:
|
||||
return {'auth_block': render_to_string('includes/auth_local.html')}
|
||||
rendered_string = {'auth_block': render_to_string('includes/auth_local.html')}
|
||||
return rendered_string
|
||||
|
||||
|
||||
def logout_block(request):
|
||||
"""Displays logout links local vs auth0."""
|
||||
if settings.AUTH0:
|
||||
return {'logout_block': render_to_string('includes/logout_auth0.html')}
|
||||
rendered_string = {'logout_block': render_to_string('includes/logout_auth0.html')}
|
||||
else:
|
||||
return {'logout_block': render_to_string('includes/logout_local.html')}
|
||||
rendered_string = {'logout_block': render_to_string('includes/logout_local.html')}
|
||||
return rendered_string
|
||||
|
||||
|
||||
def login_button(request):
|
||||
"""Displays login button local vs auth0."""
|
||||
if settings.AUTH0:
|
||||
rendered_string = {'login_button': render_to_string('includes/login_button_auth0.html')}
|
||||
else:
|
||||
rendered_string = {'login_button': render_to_string('includes/login_button_local.html')}
|
||||
return rendered_string
|
||||
|
||||
|
||||
def version(request):
|
||||
"""Displays the current satnogs-db version."""
|
||||
return {'version': 'Version: {}'.format(__version__)}
|
||||
|
||||
|
||||
def decoders_version(request):
|
||||
"""Displays the satnogsdecoders version."""
|
||||
return {'decoders_version': 'Decoders Version: {}'.format(satnogsdecoders_version)}
|
||||
|
|
|
@ -334,82 +334,5 @@
|
|||
"fields": {
|
||||
"name": "BPSK400"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 61,
|
||||
"fields": {
|
||||
"name": "OFDM"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 62,
|
||||
"fields": {
|
||||
"name": "QPSK38k4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 63,
|
||||
"fields": {
|
||||
"name": "GMSK"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 64,
|
||||
"fields": {
|
||||
"name": "WSJT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 65,
|
||||
"fields": {
|
||||
"name": "BPSK2k4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 66,
|
||||
"fields": {
|
||||
"name": "BPSK4k8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 67,
|
||||
"fields": {
|
||||
"name": "BPSK12k5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 68,
|
||||
"fields": {
|
||||
"name": "2-GFSK9k6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 69,
|
||||
"fields": {
|
||||
"name": "QPSK"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 70,
|
||||
"fields": {
|
||||
"name": "BPSK14k4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.mode",
|
||||
"pk": 71,
|
||||
"fields": {
|
||||
"name": "DQPSK"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
[
|
||||
{
|
||||
"model": "base.operator",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Libre Space Foundation",
|
||||
"names": "LSF",
|
||||
"description": "The Libre Space Foundation promotes open source space technologies.",
|
||||
"website": "https://libre.space"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.operator",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Radio Amateur Satellite Corporation",
|
||||
"names": "AMSAT",
|
||||
"description": "The goal of AMSAT is to foster Amateur Radio’s participation in space research and communication.",
|
||||
"website": "https://www.amsat.org"
|
||||
}
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -3,171 +3,126 @@
|
|||
"model": "base.telemetry",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"satellite": 8,
|
||||
"name": "ISS AX.25",
|
||||
"decoder": "ax25frames"
|
||||
"satellite": 95,
|
||||
"name": "UNISAT-6 telemetry",
|
||||
"decoder": "us6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"satellite": 32,
|
||||
"name": "STRAND-1 Telemetry",
|
||||
"decoder": "strand"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"satellite": 65,
|
||||
"name": "UNISAT-6 Telemetry",
|
||||
"decoder": "us6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"satellite": 89,
|
||||
"name": "FOX-1A Telemetry",
|
||||
"decoder": "fox"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"satellite": 172,
|
||||
"name": "QBEE Telemetry",
|
||||
"decoder": "qbee"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"satellite": 197,
|
||||
"name": "CAS-4B Telemetry",
|
||||
"decoder": "cas4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"satellite": 198,
|
||||
"satellite": 206,
|
||||
"name": "CAS-4A Telemetry",
|
||||
"decoder": "cas4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 8,
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"satellite": 203,
|
||||
"name": "SKCUBE Telemetry",
|
||||
"decoder": "skcube"
|
||||
"satellite": 205,
|
||||
"name": "CAS-4B Telemetry",
|
||||
"decoder": "cas4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 9,
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"satellite": 223,
|
||||
"name": "FOX-1B Telemetry",
|
||||
"decoder": "fox"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"satellite": 264,
|
||||
"satellite": 255,
|
||||
"name": "SiriusSat-1 Telemetry",
|
||||
"decoder": "siriussat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 11,
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"satellite": 265,
|
||||
"satellite": 256,
|
||||
"name": "SiriusSat-2 Telemetry",
|
||||
"decoder": "siriussat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 12,
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"satellite": 277,
|
||||
"satellite": 55,
|
||||
"name": "STRAND-1 Telemetry",
|
||||
"decoder": "strand"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"satellite": 219,
|
||||
"name": "SKCUBE Telemetry",
|
||||
"decoder": "skcube"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"satellite": 257,
|
||||
"name": "ELFIN-B Telemetry",
|
||||
"decoder": "elfin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 13,
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"satellite": 278,
|
||||
"satellite": 258,
|
||||
"name": "ELFIN-A Telemetry",
|
||||
"decoder": "elfin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 12,
|
||||
"fields": {
|
||||
"satellite": 11,
|
||||
"name": "ISS AX.25",
|
||||
"decoder": "ax25frames"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"satellite": 136,
|
||||
"name": "FOX-1A Telemetry",
|
||||
"decoder": "fox"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 14,
|
||||
"fields": {
|
||||
"satellite": 287,
|
||||
"name": "CubeBel-1 Telemetry",
|
||||
"decoder": "cubebel1"
|
||||
"satellite": 232,
|
||||
"name": "FOX-1B Telemetry",
|
||||
"decoder": "fox"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 15,
|
||||
"fields": {
|
||||
"satellite": 259,
|
||||
"name": "IRVINE-01 Telemetry",
|
||||
"decoder": "irvine"
|
||||
"satellite": 182,
|
||||
"name": "QBEE Telemetry",
|
||||
"decoder": "qbee"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 16,
|
||||
"fields": {
|
||||
"satellite": 308,
|
||||
"name": "MinXSS-2 Telemetry",
|
||||
"decoder": "minxss"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 17,
|
||||
"fields": {
|
||||
"satellite": 117,
|
||||
"name": "MINXSS Telemetry",
|
||||
"decoder": "minxss"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 18,
|
||||
"fields": {
|
||||
"satellite": 301,
|
||||
"name": "IRVINE-01 Telemetry",
|
||||
"decoder": "irvine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "base.telemetry",
|
||||
"pk": 19,
|
||||
"fields": {
|
||||
"satellite": 229,
|
||||
"name": "FOX-1D Telemetry",
|
||||
"decoder": "fox"
|
||||
"satellite": 24,
|
||||
"name": "FALCONSAT 3 AX.25",
|
||||
"decoder": "ax25monitor"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
153
db/base/forms.py
153
db/base/forms.py
|
@ -1,9 +1,152 @@
|
|||
from django import forms
|
||||
"""SatNOGS DB django base Forms class"""
|
||||
from bootstrap_modal_forms.forms import BSModalForm, BSModalModelForm
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms import ModelChoiceField, TextInput
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from db.base.models import Suggestion
|
||||
from db.base.models import Satellite, SatelliteEntry, Transmitter, TransmitterEntry
|
||||
|
||||
|
||||
class SuggestionForm(forms.ModelForm):
|
||||
def existing_uuid(value):
|
||||
"""ensures the UUID is existing and valid"""
|
||||
try:
|
||||
Transmitter.objects.get(uuid=value)
|
||||
except Transmitter.DoesNotExist as error:
|
||||
raise ValidationError(
|
||||
_('%(value)s is not a valid uuid'),
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
) from error
|
||||
|
||||
|
||||
class TransmitterCreateForm(BSModalModelForm): # pylint: disable=too-many-ancestors
|
||||
"""Model Form class for TransmitterEntry objects"""
|
||||
class Meta:
|
||||
model = Suggestion
|
||||
exclude = ['user', 'uuid']
|
||||
model = TransmitterEntry
|
||||
fields = [
|
||||
'description', 'type', 'status', 'uplink_low', 'uplink_high', 'uplink_drift',
|
||||
'uplink_mode', 'downlink_low', 'downlink_high', 'downlink_drift', 'downlink_mode',
|
||||
'invert', 'baud', 'citation', 'service', 'iaru_coordination', 'iaru_coordination_url',
|
||||
'itu_notification'
|
||||
]
|
||||
labels = {
|
||||
'downlink_low': _('Downlink freq.'),
|
||||
'uplink_low': _('Uplink freq.'),
|
||||
'invert': _('Inverted Transponder?'),
|
||||
'iaru_coordination': _('IARU Coordination'),
|
||||
'iaru_coordination_url': _('IARU Coordination URL'),
|
||||
'itu_notification': _('ITU Notifications URLs'),
|
||||
}
|
||||
widgets = {
|
||||
'description': TextInput(),
|
||||
}
|
||||
|
||||
|
||||
class TransmitterUpdateForm(BSModalModelForm): # pylint: disable=too-many-ancestors
|
||||
"""Model Form class for TransmitterEntry objects"""
|
||||
class Meta:
|
||||
model = TransmitterEntry
|
||||
fields = [
|
||||
'description', 'type', 'status', 'uplink_low', 'uplink_high', 'uplink_drift',
|
||||
'uplink_mode', 'downlink_low', 'downlink_high', 'downlink_drift', 'downlink_mode',
|
||||
'invert', 'baud', 'citation', 'service', 'iaru_coordination', 'iaru_coordination_url',
|
||||
'itu_notification'
|
||||
]
|
||||
labels = {
|
||||
'downlink_low': _('Downlink freq.'),
|
||||
'uplink_low': _('Uplink freq.'),
|
||||
'invert': _('Inverted Transponder?'),
|
||||
'iaru_coordination': _('IARU Coordination'),
|
||||
'iaru_coordination_url': _('IARU Coordination URL'),
|
||||
'itu_notification': _('ITU Notifications URLs'),
|
||||
}
|
||||
widgets = {
|
||||
'description': TextInput(),
|
||||
}
|
||||
|
||||
|
||||
class SatelliteCreateForm(BSModalModelForm): # pylint: disable=too-many-ancestors
|
||||
"""Form that uses django-bootstrap-modal-forms for satellite editing"""
|
||||
class Meta:
|
||||
model = SatelliteEntry
|
||||
fields = [
|
||||
'norad_cat_id', 'norad_follow_id', 'name', 'names', 'description', 'operator',
|
||||
'status', 'countries', 'website', 'dashboard_url', 'launched', 'deployed', 'decayed',
|
||||
'image', 'citation'
|
||||
]
|
||||
labels = {
|
||||
'norad_cat_id': _('Norad ID'),
|
||||
'norad_follow_id': _('Followed Norad ID'),
|
||||
'names': _('Other names'),
|
||||
'countries': _('Countries of Origin'),
|
||||
'launched': _('Launch Date'),
|
||||
'deployed': _('Deploy Date'),
|
||||
'decayed': _('Re-entry Date'),
|
||||
'description': _('Description'),
|
||||
'dashboard_url': _('Dashboard URL'),
|
||||
'operator': _('Owner/Operator'),
|
||||
}
|
||||
widgets = {'names': TextInput()}
|
||||
|
||||
|
||||
class SatelliteUpdateForm(BSModalModelForm): # pylint: disable=too-many-ancestors
|
||||
"""Form that uses django-bootstrap-modal-forms for satellite editing"""
|
||||
class Meta:
|
||||
model = SatelliteEntry
|
||||
fields = [
|
||||
'norad_cat_id', 'norad_follow_id', 'name', 'names', 'description', 'operator',
|
||||
'status', 'countries', 'website', 'dashboard_url', 'launched', 'deployed', 'decayed',
|
||||
'image', 'citation'
|
||||
]
|
||||
labels = {
|
||||
'norad_cat_id': _('Norad ID'),
|
||||
'norad_follow_id': _('Followed Norad ID'),
|
||||
'names': _('Other names'),
|
||||
'countries': _('Countries of Origin'),
|
||||
'launched': _('Launch Date'),
|
||||
'deployed': _('Deploy Date'),
|
||||
'decayed': _('Re-entry Date'),
|
||||
'description': _('Description'),
|
||||
'dashboard_url': _('Dashboard URL'),
|
||||
'operator': _('Owner/Operator'),
|
||||
}
|
||||
widgets = {'names': TextInput()}
|
||||
|
||||
|
||||
class MergeSatellitesForm(BSModalForm):
|
||||
"""Form that uses django-bootstrap-modal-forms for merging satellites"""
|
||||
primary_satellite = ModelChoiceField(
|
||||
label=_('Primary Satellite'),
|
||||
queryset=Satellite.objects.filter(
|
||||
associated_satellite__isnull=True, satellite_entry__approved=True
|
||||
),
|
||||
empty_label="Select the Primary Satellite"
|
||||
)
|
||||
associated_satellite = ModelChoiceField(
|
||||
label=_('Associated Satellite'),
|
||||
queryset=Satellite.objects.filter(
|
||||
associated_satellite__isnull=True, satellite_entry__approved=True
|
||||
),
|
||||
empty_label="Select the Associated Satellite"
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
if any(self.errors):
|
||||
# If there are errors in forms validation no need for validating the formset
|
||||
return
|
||||
|
||||
cleaned_data = super().clean()
|
||||
primary_satellite = cleaned_data.get("primary_satellite")
|
||||
associated_satellite = cleaned_data.get("associated_satellite")
|
||||
|
||||
if primary_satellite == associated_satellite:
|
||||
self.add_error(
|
||||
'associated_satellite',
|
||||
ValidationError(
|
||||
_('Associated Satellite can not be the same with the Primary Satellite'),
|
||||
code='invalid'
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = ['primary_satellite', 'associated_satellite']
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
"""Helper functions for SatNOGS DB"""
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWX'
|
||||
LOWER = 'abcdefghijklmnopqrstuvwx'
|
||||
|
||||
|
||||
def gridsquare(lat, lng):
|
||||
if not (-180 <= lng < 180):
|
||||
return False
|
||||
if not (-90 <= lat < 90):
|
||||
return False
|
||||
"""Calculates a maidenhead grid square from a lat/long
|
||||
|
||||
Used when we get a SiDS submission, we want to store and display the
|
||||
location of the submitter as a grid square.
|
||||
|
||||
:returns: a string of the grid square, ie: EM69uf
|
||||
"""
|
||||
try:
|
||||
if not -180 <= lng < 180:
|
||||
return 'Unknown'
|
||||
if not -90 <= lat < 90:
|
||||
return 'Unknown'
|
||||
except TypeError:
|
||||
return 'Unknown'
|
||||
|
||||
adj_lat = lat + 90.0
|
||||
adj_lon = lng + 180.0
|
||||
|
@ -28,40 +37,22 @@ def gridsquare(lat, lng):
|
|||
grid_lat_subsq = LOWER[int(adj_lat_remainder / 2.5)]
|
||||
grid_lon_subsq = LOWER[int(adj_lon_remainder / 5)]
|
||||
|
||||
qth = '{}'.format(grid_lon_sq + grid_lat_sq + grid_lon_field +
|
||||
grid_lat_field + grid_lon_subsq + grid_lat_subsq)
|
||||
qth = '{}'.format(
|
||||
grid_lon_sq + grid_lat_sq + grid_lon_field + grid_lat_field + grid_lon_subsq
|
||||
+ grid_lat_subsq
|
||||
)
|
||||
|
||||
return qth
|
||||
|
||||
|
||||
def get_apikey(user):
|
||||
def get_api_token(user):
|
||||
"""If necessary, create, then return an API Token for a user
|
||||
|
||||
:param user: a SatNOGS DB User object
|
||||
:returns: user API token
|
||||
"""
|
||||
try:
|
||||
token = Token.objects.get(user=user)
|
||||
except Exception:
|
||||
except ObjectDoesNotExist:
|
||||
token = Token.objects.create(user=user)
|
||||
return token
|
||||
|
||||
|
||||
def cache_get_key(*args, **kwargs):
|
||||
import hashlib
|
||||
serialise = []
|
||||
for arg in args:
|
||||
serialise.append(str(arg))
|
||||
for key, arg in kwargs.items():
|
||||
serialise.append(str(key))
|
||||
serialise.append(str(arg))
|
||||
key = hashlib.md5("".join(serialise)).hexdigest()
|
||||
return key
|
||||
|
||||
|
||||
def cache_for(time):
|
||||
def decorator(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
key = cache_get_key(fn.__name__, *args, **kwargs)
|
||||
result = cache.get(key)
|
||||
if not result:
|
||||
result = fn(*args, **kwargs)
|
||||
cache.set(key, result, time)
|
||||
return result
|
||||
return wrapper
|
||||
return decorator
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from db.base.models import Satellite, Telemetry, DemodData
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Decode Satellite data'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
# Positional arguments
|
||||
parser.add_argument('satellite_identifiers',
|
||||
nargs='+',
|
||||
metavar='<Satellite Identifier>')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for item in options['satellite_identifiers']:
|
||||
satellites = Satellite.objects.all()
|
||||
for satellite in satellites:
|
||||
if satellite.has_telemetry_decoders:
|
||||
data = DemodData.objects.filter(satellite=satellite).filter(payload_decoded='')
|
||||
telemetry_decoders = Telemetry.objects.filter(satellite=satellite)
|
||||
|
||||
for obj in data:
|
||||
for option in telemetry_decoders:
|
||||
decoder_module = 'db.base.decoders.{0}'.format(option.decoder)
|
||||
decoder = __import__(decoder_module, fromlist='.')
|
||||
|
||||
with open(obj.payload_frame.path) as fp:
|
||||
frame = fp.read()
|
||||
|
||||
try:
|
||||
payload_decoded = decoder.decode_payload(frame, obj.timestamp,
|
||||
obj.data_id)
|
||||
except ValueError:
|
||||
obj.payload_decoded = ''
|
||||
obj.payload_telemetry = None
|
||||
obj.save()
|
||||
continue
|
||||
else:
|
||||
obj.payload_decoded = payload_decoded
|
||||
obj.payload_telemetry = option
|
||||
obj.save()
|
||||
break
|
|
@ -1,22 +0,0 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from db.base.models import Satellite
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Delete selected Satellites'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
# Positional arguments
|
||||
parser.add_argument('norad_ids',
|
||||
nargs='+',
|
||||
metavar='<norad id>')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for norad_id in options['norad_ids']:
|
||||
try:
|
||||
Satellite.objects.get(norad_cat_id=norad_id).delete()
|
||||
self.stdout.write('Deleted satellite {}.'.format(norad_id))
|
||||
continue
|
||||
except Exception:
|
||||
self.stderr.write('Satellite with Identifier {} does not exist'.format(norad_id))
|
|
@ -1,52 +0,0 @@
|
|||
import requests
|
||||
from datetime import datetime, timedelta
|
||||
from pytz import timezone
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.conf import settings
|
||||
|
||||
from db.base.models import Satellite, Transmitter, DemodData
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Fetch Satellite data from Network'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
apiurl = settings.NETWORK_API_ENDPOINT
|
||||
data_url = "{0}data".format(apiurl)
|
||||
start_date = datetime.utcnow() - timedelta(days=int(settings.DATA_FETCH_DAYS))
|
||||
start_date = datetime.strftime(start_date, '%Y-%m-%dT%H:%M:%SZ')
|
||||
params = {'start': start_date}
|
||||
response = requests.get(data_url, params=params)
|
||||
|
||||
satellites = Satellite.objects.all()
|
||||
|
||||
for obj in response.json():
|
||||
norad_cat_id = obj['norad_cat_id']
|
||||
data_id = obj['id']
|
||||
station = obj['station_name']
|
||||
lat = obj['station_lat']
|
||||
lng = obj['station_lng']
|
||||
try:
|
||||
satellite = satellites.get(norad_cat_id=norad_cat_id)
|
||||
except Satellite.DoesNotExist:
|
||||
continue
|
||||
try:
|
||||
transmitter = Transmitter.objects.get(uuid=obj['transmitter'])
|
||||
except Transmitter.DoesNotExist:
|
||||
transmitter = None
|
||||
|
||||
DemodData.objects.filter(data_id=data_id).delete()
|
||||
|
||||
for demoddata in obj['demoddata']:
|
||||
payload_url = demoddata['payload_demod']
|
||||
timestamp = datetime.strptime(payload_url.split('/')[-1].split('_')[0],
|
||||
'%Y%m%dT%H%M%SZ').replace(tzinfo=timezone('UTC'))
|
||||
frame = str(requests.get(payload_url).json())
|
||||
payload_frame = ContentFile(frame, name='network')
|
||||
|
||||
DemodData.objects.create(satellite=satellite, transmitter=transmitter,
|
||||
data_id=data_id, payload_frame=payload_frame,
|
||||
timestamp=timestamp, source='network',
|
||||
station=station, lat=lat, lng=lng)
|
|
@ -1,21 +0,0 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from db.base.tasks import update_satellite
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Updates/Inserts Name for certain Satellites'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
# Positional arguments
|
||||
parser.add_argument('norad_ids',
|
||||
nargs='+',
|
||||
metavar='<norad id>')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for norad_id in options['norad_ids']:
|
||||
try:
|
||||
update_satellite(int(norad_id), update_name=True, update_tle=False)
|
||||
except LookupError:
|
||||
self.stderr.write('Satellite {} not found in Celestrak'.format(norad_id))
|
||||
continue
|
|
@ -1,8 +1,10 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
"""SatNOGS DB django management command to initialize a new database"""
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""django management command to initialize a new database"""
|
||||
help = 'Create initial fixtures'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
@ -10,9 +12,12 @@ class Command(BaseCommand):
|
|||
self.stdout.write("Creating database...")
|
||||
call_command('migrate')
|
||||
|
||||
# Initial data
|
||||
# Initial data
|
||||
self.stdout.write("Creating fixtures...")
|
||||
call_command('loaddata', 'modes')
|
||||
call_command('loaddata', 'operators')
|
||||
call_command('loaddata', 'satelliteidentifiers')
|
||||
call_command('loaddata', 'satelliteentries')
|
||||
call_command('loaddata', 'satellites')
|
||||
call_command('loaddata', 'transmitters')
|
||||
call_command('loaddata', 'telemetries')
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
"""SatNOGS DB django management command to update TLE entries"""
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from db.base.tasks import update_all_tle
|
||||
from db.base.tasks import update_tle_sets
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""django management command to update TLE entries"""
|
||||
help = 'Update TLEs for existing Satellites'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
update_all_tle()
|
||||
update_tle_sets()
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
import shortuuidfield.fields
|
||||
import django.core.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Mode',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(unique=True, max_length=10)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Satellite',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('norad_cat_id', models.PositiveIntegerField()),
|
||||
('name', models.CharField(max_length=45)),
|
||||
('names', models.TextField(blank=True)),
|
||||
('image', models.ImageField(upload_to=b'satellites', blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Transmitter',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('uuid', shortuuidfield.fields.ShortUUIDField(db_index=True, unique=True, max_length=22, editable=False, blank=True)),
|
||||
('description', models.TextField()),
|
||||
('alive', models.BooleanField(default=True)),
|
||||
('uplink_low', models.PositiveIntegerField(null=True, blank=True)),
|
||||
('uplink_high', models.PositiveIntegerField(null=True, blank=True)),
|
||||
('downlink_low', models.PositiveIntegerField(null=True, blank=True)),
|
||||
('downlink_high', models.PositiveIntegerField(null=True, blank=True)),
|
||||
('invert', models.BooleanField(default=False)),
|
||||
('baud', models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('approved', models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Suggestion',
|
||||
fields=[
|
||||
('transmitter_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='base.Transmitter')),
|
||||
('citation', models.CharField(max_length=255, blank=True)),
|
||||
],
|
||||
bases=('base.transmitter',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitter',
|
||||
name='mode',
|
||||
field=models.ForeignKey(related_name='transmitters', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='base.Mode', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitter',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(related_name='transmitters', to='base.Satellite', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='suggestion',
|
||||
name='transmitter',
|
||||
field=models.ForeignKey(related_name='suggestions', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='base.Transmitter', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='suggestion',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
]
|
|
@ -1,14 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.7 on 2018-01-07 12:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import db.base.models
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import shortuuidfield.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import db.base.models
|
||||
|
||||
# Functions from the following migrations need manual copying.
|
||||
# Move them and any dependencies into this file, then update the
|
||||
|
@ -17,7 +15,7 @@ import shortuuidfield.fields
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [(b'base', '0001_initial'), (b'base', '0002_auto_20150908_2054'), (b'base', '0003_auto_20160504_2104'), (b'base', '0004_auto_20170302_1641'), (b'base', '0005_demoddata_observer'), (b'base', '0006_auto_20170323_1715'), (b'base', '0007_satellite_status'), (b'base', '0008_satellite_description'), (b'base', '0009_auto_20180103_1931')]
|
||||
replaces = [('base', '0001_initial'), ('base', '0002_auto_20150908_2054'), ('base', '0003_auto_20160504_2104'), ('base', '0004_auto_20170302_1641'), ('base', '0005_demoddata_observer'), ('base', '0006_auto_20170323_1715'), ('base', '0007_satellite_status'), ('base', '0008_satellite_description'), ('base', '0009_auto_20180103_1931')]
|
||||
|
||||
initial = True
|
||||
|
||||
|
@ -40,10 +38,10 @@ class Migration(migrations.Migration):
|
|||
('norad_cat_id', models.PositiveIntegerField()),
|
||||
('name', models.CharField(max_length=45)),
|
||||
('names', models.TextField(blank=True)),
|
||||
('image', models.ImageField(blank=True, help_text=b'Ideally: 250x250', upload_to=b'satellites')),
|
||||
('image', models.ImageField(blank=True, help_text='Ideally: 250x250', upload_to='satellites')),
|
||||
('tle1', models.CharField(blank=True, max_length=200)),
|
||||
('tle2', models.CharField(blank=True, max_length=200)),
|
||||
('status', models.CharField(choices=[(b'alive', b'alive'), (b'dead', b'dead'), (b're-entered', b're-entered')], default=b'alive', max_length=10)),
|
||||
('status', models.CharField(choices=[('alive', 'alive'), ('dead', 'dead'), ('re-entered', 're-entered')], default='alive', max_length=10)),
|
||||
('description', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
|
@ -105,8 +103,8 @@ class Migration(migrations.Migration):
|
|||
('payload_decoded', models.TextField(blank=True)),
|
||||
('payload_frame', models.FileField(blank=True, null=True, upload_to=db.base.models._name_payload_frame)),
|
||||
('satellite', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='telemetry_data', to='base.Satellite')),
|
||||
('source', models.CharField(choices=[(b'manual', b'manual'), (b'network', b'network'), (b'sids', b'sids')], default=b'sids', max_length=7)),
|
||||
('station', models.CharField(default=b'Unknown', max_length=45)),
|
||||
('source', models.CharField(choices=[('manual', 'manual'), ('network', 'network'), ('sids', 'sids')], default='sids', max_length=7)),
|
||||
('station', models.CharField(default='Unknown', max_length=45)),
|
||||
('timestamp', models.DateTimeField(null=True)),
|
||||
],
|
||||
options={
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='satellite',
|
||||
name='image',
|
||||
field=models.ImageField(help_text=b'Ideally: 250x250', upload_to=b'satellites', blank=True),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.10 on 2018-03-04 08:50
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.6 on 2016-05-04 21:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import jsonfield.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0002_auto_20150908_2054'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DemodData',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('data_id', models.PositiveIntegerField()),
|
||||
('payload', jsonfield.fields.JSONField(default=dict)),
|
||||
('transmitter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.Transmitter')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='telemetry_decoder',
|
||||
field=models.CharField(blank=True, max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='telemetry_schema',
|
||||
field=jsonfield.fields.JSONField(blank=True, default=dict),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-09-19 00:28
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-02 16:41
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import db.base.models
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0003_auto_20160504_2104'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Telemetry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=45)),
|
||||
('schema', models.TextField(blank=True)),
|
||||
('decoder', models.CharField(blank=True, max_length=20)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['satellite__norad_cat_id'],
|
||||
'verbose_name_plural': 'Telemetries',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='demoddata',
|
||||
options={'ordering': ['-timestamp']},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='satellite',
|
||||
options={'ordering': ['norad_cat_id']},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='demoddata',
|
||||
name='payload',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='satellite',
|
||||
name='telemetry_decoder',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='satellite',
|
||||
name='telemetry_schema',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='lat',
|
||||
field=models.FloatField(default=0, validators=[django.core.validators.MaxValueValidator(90), django.core.validators.MinValueValidator(-90)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='lng',
|
||||
field=models.FloatField(default=0, validators=[django.core.validators.MaxValueValidator(180), django.core.validators.MinValueValidator(-180)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='payload_decoded',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='payload_frame',
|
||||
field=models.FileField(blank=True, null=True, upload_to=db.base.models._name_payload_frame),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='telemetry_data', to='base.Satellite'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='source',
|
||||
field=models.CharField(choices=[(b'manual', b'manual'), (b'network', b'network'), (b'sids', b'sids')], default=b'sids', max_length=7),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='station',
|
||||
field=models.CharField(default=b'Unknown', max_length=45),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='timestamp',
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='demoddata',
|
||||
name='data_id',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='demoddata',
|
||||
name='transmitter',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='base.Transmitter'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='telemetry',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='telemetries', to='base.Satellite'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='payload_telemetry',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='base.Telemetry'),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-12-15 11:30
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
@ -20,7 +18,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='transmitter',
|
||||
name='type',
|
||||
field=models.CharField(choices=[(b'Transmitter', b'Transmitter'), (b'Transceiver', b'Transceiver'), (b'Transponder', b'Transponder')], default=b'Transmitter', max_length=11),
|
||||
field=models.CharField(choices=[('Transmitter', 'Transmitter'), ('Transceiver', 'Transceiver'), ('Transponder', 'Transponder')], default='Transmitter', max_length=11),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitter',
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.6 on 2017-03-14 18:38
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0004_auto_20170302_1641'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='observer',
|
||||
field=models.CharField(blank=True, max_length=60),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2019-01-19 16:39
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.6 on 2017-03-23 17:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0005_demoddata_observer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='tle1',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='tle2',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2019-01-21 13:20
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.6 on 2017-05-08 15:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0006_auto_20170323_1715'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='status',
|
||||
field=models.CharField(choices=[(b'alive', b'alive'), (b'dead', b'dead'), (b're-entered', b're-entered')], default=b'alive', max_length=10),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,55 @@
|
|||
# Generated by Django 1.11.11 on 2019-03-29 19:11
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def add_suggestion_permissions(apps, schema_editor):
|
||||
pass
|
||||
|
||||
|
||||
def remove_suggestion_permissions(apps, schema_editor):
|
||||
"""Reverse the above additions of permissions."""
|
||||
ContentType = apps.get_model('contenttypes.ContentType')
|
||||
Permission = apps.get_model('auth.Permission')
|
||||
try:
|
||||
content_type = ContentType.objects.get(
|
||||
model='suggestion',
|
||||
app_label='base',
|
||||
)
|
||||
# This cascades to Group
|
||||
Permission.objects.filter(
|
||||
content_type=content_type,
|
||||
codename__in=('add_suggestion', 'change_suggestion', 'delete_suggestion'),
|
||||
).delete()
|
||||
except ContentType.DoesNotExist:
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0006_auto_20190121_1320'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='suggestion',
|
||||
name='transmitter',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='suggestion',
|
||||
name='citation',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='suggestion',
|
||||
name='user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Suggestion',
|
||||
),
|
||||
migrations.RunPython(remove_suggestion_permissions, add_suggestion_permissions),
|
||||
migrations.RenameModel(
|
||||
old_name='Transmitter',
|
||||
new_name='TransmitterEntry',
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.1 on 2017-05-16 21:09
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0007_satellite_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='description',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,127 @@
|
|||
# Generated by Django 1.11.11 on 2019-04-04 04:36
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import shortuuidfield.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.db.models import Max
|
||||
|
||||
|
||||
def from_alive_to_status(apps, schema_editor):
|
||||
Transmitter = apps.get_model('base', 'Transmitter')
|
||||
Transmitter.objects.filter(alive=True).update(status='active')
|
||||
Transmitter.objects.filter(alive=False).update(status='inactive')
|
||||
|
||||
def reverse_from_alive_to_status(apps, schema_editor):
|
||||
Transmitter = apps.get_model('base', 'Transmitter')
|
||||
Transmitter.objects.filter(status='active').update(alive=True)
|
||||
Transmitter.objects.filter(status='inactive').update(alive=False)
|
||||
Transmitter.objects.filter(status='invalid').update(alive=False)
|
||||
|
||||
def mark_approved_as_reviewed(apps, schema_editor):
|
||||
Transmitter = apps.get_model('base', 'Transmitter')
|
||||
Transmitter.objects.filter(approved=True).update(reviewed=True)
|
||||
|
||||
def reverse_mark_approved_as_reviewed(apps, schema_editor):
|
||||
pass
|
||||
|
||||
def allow_latest(apps, schema_editor):
|
||||
pass
|
||||
|
||||
def keep_only_latest(apps, schema_editor):
|
||||
Transmitter = apps.get_model('base', 'Transmitter')
|
||||
uuids = set(Transmitter.objects.values_list('uuid', flat=True))
|
||||
for uuid in uuids:
|
||||
entries = Transmitter.objects.filter(uuid=uuid)
|
||||
if entries.count() > 1:
|
||||
created_max = entries.aggregate(Max('created'))['created__max']
|
||||
for entry in entries:
|
||||
if entry.created != created_max:
|
||||
entry.delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('base', '0007_transmitter_rename'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Transmitter',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
},
|
||||
bases=('base.transmitterentry',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TransmitterSuggestion',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'indexes': [],
|
||||
'proxy': True,
|
||||
'permissions': (('approve', 'Can approve/reject transmitter suggestions'),),
|
||||
},
|
||||
bases=('base.transmitterentry',),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='transmitterentry',
|
||||
options={'verbose_name_plural': 'Transmitter entries'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='citation',
|
||||
field=models.CharField(default='CITATION NEEDED - https://xkcd.com/285/', max_length=512),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='created',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='reviewed',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('active', 'active'), ('inactive', 'inactive'), ('invalid', 'invalid')], default='active', max_length=8),
|
||||
),
|
||||
migrations.RunPython(from_alive_to_status, reverse_from_alive_to_status),
|
||||
migrations.RemoveField(
|
||||
model_name='transmitterentry',
|
||||
name='alive',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='mode',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transmitter_entries', to='base.Mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transmitter_entries', to='base.Satellite'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='uuid',
|
||||
field=shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, max_length=22),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='transmitterentry',
|
||||
unique_together=set([('uuid', 'created')]),
|
||||
),
|
||||
migrations.RunPython(mark_approved_as_reviewed, reverse_mark_approved_as_reviewed),
|
||||
migrations.RunPython(allow_latest, keep_only_latest),
|
||||
]
|
|
@ -1,40 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.7 on 2018-01-03 19:31
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
def move_payloads(apps, schema_editor):
|
||||
DemodData = apps.get_model('base', 'DemodData')
|
||||
for demod in DemodData.objects.all():
|
||||
try:
|
||||
folder = 'payload_frames/{0}/{1}/{2}/'.format(demod.timestamp.year,
|
||||
demod.timestamp.month,
|
||||
demod.timestamp.day)
|
||||
except AttributeError:
|
||||
folder = 'payload_frames/{0}/{1}/{2}/'.format(now().year, now().month, now().day)
|
||||
fullpath = '{0}/{1}'.format(settings.MEDIA_ROOT, folder)
|
||||
if not os.path.exists(fullpath):
|
||||
os.makedirs(fullpath)
|
||||
filename = demod.payload_frame.name.split('/')[-1]
|
||||
new_name = '{0}{1}'.format(folder, filename)
|
||||
new_path = '{0}/{1}'.format(settings.MEDIA_ROOT, new_name)
|
||||
os.rename(demod.payload_frame.path, new_path)
|
||||
demod.payload_frame.name = new_name
|
||||
demod.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0008_satellite_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(move_payloads),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 1.11.20 on 2019-04-20 23:06
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0008_transmitter_model_change'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='demoddata',
|
||||
old_name='source',
|
||||
new_name='app_source',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,39 @@
|
|||
# Generated by Django 1.11.20 on 2019-04-21 01:43
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0009_auto_20190420_2306'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='demoddata',
|
||||
name='payload_telemetry',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='base.Telemetry'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='demoddata',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='telemetry_data', to='base.Satellite'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='demoddata',
|
||||
name='transmitter',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='base.TransmitterEntry'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='telemetry',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='telemetries', to='base.Satellite'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transmitter_entries', to='base.Satellite'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 1.11.20 on 2019-07-02 05:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0010_auto_20190421_0143'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='service',
|
||||
field=models.CharField(choices=[('Aeronautical', 'Aeronautical'), ('Amateur', 'Amateur'), ('Broadcasting', 'Broadcasting'), ('Earth Exploration', 'Earth Exploration'), ('Fixed', 'Fixed'), ('Inter-satellite', 'Inter-satellite'), ('Maritime', 'Maritime'), ('Meteorological', 'Meteorological'), ('Mobile', 'Mobile'), ('Radiolocation', 'Radiolocation'), ('Radionavigational', 'Radionavigational'), ('Space Operation', 'Space Operation'), ('Space Research', 'Space Research'), ('Standard Frequency and Time Signal', 'Standard Frequency and Time Signal'), ('Unknown', 'Unknown')], default='Unknown', max_length=34),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.6 on 2019-11-08 18:40
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0011_transmitterentry_service'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='transmitterentry',
|
||||
old_name='mode',
|
||||
new_name='downlink_mode',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,40 @@
|
|||
# Generated by Django 2.2.6 on 2019-11-08 18:42
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
from django.db.models import F
|
||||
|
||||
|
||||
def copy_field(apps, schema_editor):
|
||||
models = apps.get_model('base', 'TransmitterEntry')
|
||||
modes = apps.get_model('base', 'Mode')
|
||||
for transmitter in models.objects.all().iterator():
|
||||
if transmitter.type != "Transmitter":
|
||||
transmitter.uplink_mode = transmitter.downlink_mode
|
||||
if transmitter.invert == True:
|
||||
if transmitter.downlink_mode.name == "USB":
|
||||
transmitter.uplink_mode = modes.objects.get(name="LSB")
|
||||
elif transmitter.downlink_mode.name == "LSB":
|
||||
transmitter.uplink_mode = modes.objects.get(name="USB")
|
||||
|
||||
transmitter.save()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0012_auto_20191108_1840'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='uplink_mode',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transmitter_uplink_entries', to='base.Mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='downlink_mode',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transmitter_downlink_entries', to='base.Mode'),
|
||||
),
|
||||
migrations.RunPython(copy_field, reverse_code=migrations.RunPython.noop),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.9 on 2020-02-01 20:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0013_auto_20191108_1842'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mode',
|
||||
name='name',
|
||||
field=models.CharField(max_length=12, unique=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.12 on 2020-04-07 09:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0014_increase_char_limit_on_mode_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='dashboard_url',
|
||||
field=models.URLField(blank=True, null=True, max_length=200),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.10 on 2020-04-10 11:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0015_satellite_dashboard_url'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mode',
|
||||
name='name',
|
||||
field=models.CharField(max_length=25, unique=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 2.2.11 on 2020-05-20 14:13
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import db.base.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('base', '0016_increase_mode_name_char_limit'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ExportedFrameset',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('exported_file', models.FileField(blank=True, null=True, upload_to=db.base.models._name_exported_frames)),
|
||||
('start', models.DateTimeField(blank=True, null=True)),
|
||||
('end', models.DateTimeField(blank=True, null=True)),
|
||||
('satellite', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='base.Satellite')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 2.2.12 on 2020-05-20 16:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0017_exported_frameset'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Artifact',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('artifact_file', models.FileField(blank=True, null=True, upload_to='artifacts/')),
|
||||
('network_obs_id', models.BigIntegerField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,50 @@
|
|||
# Generated by Django 2.2.14 on 2020-07-15 12:02
|
||||
|
||||
import django.db.models.deletion
|
||||
import django_countries.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0018_artifact'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Operator',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, unique=True)),
|
||||
('names', models.TextField(blank=True)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='countries',
|
||||
field=django_countries.fields.CountryField(blank=True, max_length=746, multiple=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='deployed',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='launched',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='website',
|
||||
field=models.URLField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='operator',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='satellite_operator', to='base.Operator'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,106 @@
|
|||
# Generated by Django 2.2.14 on 2020-08-02 18:04
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0019_satellite_details'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='operator',
|
||||
name='website',
|
||||
field=models.URLField(blank=True, validators=[django.core.validators.URLValidator(regex="(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$", schemes=['http', 'https'])]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='satellite',
|
||||
name='dashboard_url',
|
||||
field=models.URLField(blank=True, null=True, validators=[django.core.validators.URLValidator(regex="(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$", schemes=['http', 'https'])]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='satellite',
|
||||
name='website',
|
||||
field=models.URLField(blank=True, validators=[django.core.validators.URLValidator(regex="(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$", schemes=['http', 'https'])]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='baud',
|
||||
field=models.FloatField(blank=True, help_text='The number of modulated symbols that the transmitter sends every second', null=True, validators=[django.core.validators.MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='citation',
|
||||
field=models.CharField(default='CITATION NEEDED - https://xkcd.com/285/', help_text='A reference (preferrably URL) for this entry or edit', max_length=512),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='created',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Timestamp for this entry or edit'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='description',
|
||||
field=models.TextField(help_text='Short description for this entry, like: UHF 9k6 AFSK Telemetry'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='downlink_drift',
|
||||
field=models.IntegerField(blank=True, help_text='Transmitter drift from the published downlink frequency, stored in parts per billion (PPB)', null=True, validators=[django.core.validators.MinValueValidator(-99999), django.core.validators.MaxValueValidator(99999)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='downlink_high',
|
||||
field=models.BigIntegerField(blank=True, help_text='Frequency (in Hz) for the top of the downlink range for a transponder', null=True, validators=[django.core.validators.MinValueValidator(0, message='Ensure this value is greater than or equal to 0Hz'), django.core.validators.MaxValueValidator(40000000000, message='Ensure this value is less than or equal to 40Ghz')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='downlink_low',
|
||||
field=models.BigIntegerField(blank=True, help_text='Frequency (in Hz) for the downlink, or bottom of the downlink range for a transponder', null=True, validators=[django.core.validators.MinValueValidator(0, message='Ensure this value is greater than or equal to 0Hz'), django.core.validators.MaxValueValidator(40000000000, message='Ensure this value is less than or equal to 40Ghz')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='downlink_mode',
|
||||
field=models.ForeignKey(blank=True, help_text='Modulation mode for the downlink', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transmitter_downlink_entries', to='base.Mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='invert',
|
||||
field=models.BooleanField(default=False, help_text='True if this is an inverted transponder'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='service',
|
||||
field=models.CharField(choices=[('Aeronautical', 'Aeronautical'), ('Amateur', 'Amateur'), ('Broadcasting', 'Broadcasting'), ('Earth Exploration', 'Earth Exploration'), ('Fixed', 'Fixed'), ('Inter-satellite', 'Inter-satellite'), ('Maritime', 'Maritime'), ('Meteorological', 'Meteorological'), ('Mobile', 'Mobile'), ('Radiolocation', 'Radiolocation'), ('Radionavigational', 'Radionavigational'), ('Space Operation', 'Space Operation'), ('Space Research', 'Space Research'), ('Standard Frequency and Time Signal', 'Standard Frequency and Time Signal'), ('Unknown', 'Unknown')], default='Unknown', help_text='The published usage category for this transmitter', max_length=34),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('active', 'active'), ('inactive', 'inactive'), ('invalid', 'invalid')], default='active', help_text='Functional state of this transmitter', max_length=8),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='uplink_drift',
|
||||
field=models.IntegerField(blank=True, help_text='Receiver drift from the published uplink frequency, stored in parts per billion (PPB)', null=True, validators=[django.core.validators.MinValueValidator(-99999), django.core.validators.MaxValueValidator(99999)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='uplink_high',
|
||||
field=models.BigIntegerField(blank=True, help_text='Frequency (in Hz) for the top of the uplink range for a transponder', null=True, validators=[django.core.validators.MinValueValidator(0, message='Ensure this value is greater than or equal to 0Hz'), django.core.validators.MaxValueValidator(40000000000, message='Ensure this value is less than or equal to 40Ghz')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='uplink_low',
|
||||
field=models.BigIntegerField(blank=True, help_text='Frequency (in Hz) for the uplink, or bottom of the uplink range for a transponder', null=True, validators=[django.core.validators.MinValueValidator(0, message='Ensure this value is greater than or equal to 0Hz'), django.core.validators.MaxValueValidator(40000000000, message='Ensure this value is less than or equal to 40Ghz')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='uplink_mode',
|
||||
field=models.ForeignKey(blank=True, help_text='Modulation mode for the uplink', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transmitter_uplink_entries', to='base.Mode'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.15 on 2020-08-08 17:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0020_auto_20200802_1804'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='demoddata',
|
||||
name='timestamp',
|
||||
field=models.DateTimeField(db_index=True, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,53 @@
|
|||
# Generated by Django 2.2.14 on 2020-08-03 02:04
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0021_auto_20200808_1725'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Tle',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('tle0', models.CharField(blank=True, max_length=69, validators=[django.core.validators.MinLengthValidator(1), django.core.validators.MaxLengthValidator(69)])),
|
||||
('tle1', models.CharField(blank=True, max_length=69, validators=[django.core.validators.MinLengthValidator(69), django.core.validators.MaxLengthValidator(69)])),
|
||||
('tle2', models.CharField(blank=True, max_length=69, validators=[django.core.validators.MinLengthValidator(69), django.core.validators.MaxLengthValidator(69)])),
|
||||
('tle_source', models.CharField(blank=True, max_length=300)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('satellite', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tles', to='base.Satellite')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-updated'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LatestTle',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('base.tle',),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tle',
|
||||
index=models.Index(fields=['-updated'], name='base_tle_updated_8936f7_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tle',
|
||||
index=models.Index(fields=['tle1', 'tle2', 'tle_source', 'satellite'], name='base_tle_tle1_30ea48_idx'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='tle',
|
||||
constraint=models.UniqueConstraint(fields=('tle1', 'tle2', 'tle_source', 'satellite'), name='unique_entry_from_source_for_satellite'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 2.2.14 on 2020-08-05 04:07
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0022_add_tle_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='tle',
|
||||
options={'ordering': ['-updated'], 'permissions': [('access_all_tles', 'Access all TLEs')]},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.2.15 on 2020-09-06 15:18
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0023_add_tle_model_permission'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='coordination',
|
||||
field=models.CharField(blank=True, choices=[('ITU Requested', 'ITU Requested'), ('ITU Rejected', 'ITU Rejected'), ('ITU Coordinated', 'ITU Coordinated'), ('IARU Requested', 'IARU Requested'), ('IARU Rejected', 'IARU Rejected'), ('IARU Coordinated', 'IARU Coordinated'), ('Uncoordinated', 'Uncoordinated')], default='', help_text='Frequency coordination status for this transmitter', max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='coordination_url',
|
||||
field=models.URLField(blank=True, help_text='URL for more details on this frequency coordination', validators=[django.core.validators.URLValidator(regex="(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$", schemes=['http', 'https'])]),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 2.2.14 on 2020-09-14 15:06
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0024_frequency_coordination_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='satellite',
|
||||
name='tle1',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='satellite',
|
||||
name='tle2',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='satellite',
|
||||
name='tle_source',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.14 on 2020-09-17 09:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0025_remove_satellite_tle_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='satellite',
|
||||
name='norad_follow_id',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.14 on 2020-09-21 16:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0026_add_satellite_norad_follow_id_field'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='satellite',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('alive', 'alive'), ('dead', 'dead'), ('future', 'future'), ('re-entered', 're-entered')], default='alive', max_length=10),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 2.2.14 on 2020-09-22 14:22
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0027_add_future_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveConstraint(
|
||||
model_name='tle',
|
||||
name='unique_entry_from_source_for_satellite',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='tle',
|
||||
name='base_tle_tle1_30ea48_idx',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,27 @@
|
|||
# Generated by Django 2.2.14 on 2020-09-22 14:23
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0028_remove_tle_constraint'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LatestTleSet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('last_modified', models.DateTimeField(auto_now=True)),
|
||||
('latest', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='latest', to='base.Tle')),
|
||||
('latest_distributable', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='latest_distributable', to='base.Tle')),
|
||||
('satellite', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='latest_tle_set', to='base.Satellite')),
|
||||
],
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='LatestTle',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.16 on 2020-09-23 12:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0029_change_latest_tle_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='telemetry',
|
||||
name='decoder',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.2.16 on 2020-10-11 14:09
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0030_increase_size_of_decoder_field'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='tle',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tles', to='base.Satellite'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.1.5 on 2021-01-20 00:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0031_set_tle_ondelete_null'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='version',
|
||||
field=models.CharField(blank=True, max_length=45),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.1.5 on 2021-01-30 04:38
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0032_sids_version'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='observation_id',
|
||||
field=models.IntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.1.5 on 2021-02-04 09:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0033_add_observations_id_in_demoddata_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='station_id',
|
||||
field=models.IntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-07 15:23
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0034_add_station_id_in_demoddata_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='transmitterentry',
|
||||
old_name='user',
|
||||
new_name='created_by',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-07 17:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0035_rename_user_field_on_transmitter_entry_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='transmitterentry',
|
||||
old_name='reviewed',
|
||||
new_name='is_reviewed',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,86 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-08 22:59
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
def add_review_details(apps, schema_editor):
|
||||
TransmitterEntry = apps.get_model('base', 'TransmitterEntry')
|
||||
orphans = TransmitterEntry.objects.filter(created_by__isnull=True, is_reviewed=True)
|
||||
for entry in orphans:
|
||||
entry.reviewed = entry.created
|
||||
entry.save()
|
||||
ids = []
|
||||
non_orphans = TransmitterEntry.objects.filter(created_by__isnull=False, is_reviewed=True).order_by('created')
|
||||
for entry in non_orphans:
|
||||
if entry.id in ids:
|
||||
entry.delete()
|
||||
continue
|
||||
next_entries = TransmitterEntry.objects.filter(created_by__isnull=False, is_reviewed=True).filter(uuid=entry.uuid).filter(created__gt=entry.created).order_by('created')
|
||||
for next_entry in next_entries:
|
||||
if (entry.uuid == next_entry.uuid and entry.description == next_entry.description and
|
||||
entry.status == next_entry.status and entry.type == next_entry.type and entry.uplink_low == next_entry.uplink_low and
|
||||
entry.uplink_high == next_entry.uplink_high and entry.uplink_drift == next_entry.uplink_drift and
|
||||
entry.downlink_low == next_entry.downlink_low and entry.downlink_high == next_entry.downlink_high and
|
||||
entry.downlink_drift == next_entry.downlink_drift and entry.downlink_mode == next_entry.downlink_mode and
|
||||
entry.uplink_mode == next_entry.uplink_mode and entry.invert == next_entry.invert and entry.baud == next_entry.baud and
|
||||
entry.satellite == next_entry.satellite and entry.reviewed == next_entry.reviewed and entry.approved == next_entry.approved and
|
||||
entry.citation == next_entry.citation and entry.service == next_entry.service and entry.coordination == next_entry.coordination and
|
||||
entry.coordination_url == next_entry.coordination_url):
|
||||
ids.append(next_entry.id)
|
||||
entry.reviewed = next_entry.created
|
||||
entry.reviewer = next_entry.created_by
|
||||
entry.save()
|
||||
break
|
||||
else:
|
||||
entry.reviewed = entry.created
|
||||
entry.reviewer = entry.created_by
|
||||
entry.save()
|
||||
|
||||
|
||||
def remove_review_details(apps, schema_editor):
|
||||
TransmitterEntry = apps.get_model('base', 'TransmitterEntry')
|
||||
non_orphans = TransmitterEntry.objects.filter(created_by__isnull=False, is_reviewed=True).order_by('created')
|
||||
for entry in non_orphans:
|
||||
entry.pk = None
|
||||
entry.created = entry.reviewed
|
||||
entry.created_by = entry.reviewer
|
||||
entry.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('base', '0036_rename_reviewed_field_on_transmitter_entry_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='reviewed',
|
||||
field=models.DateTimeField(blank=True, help_text='Timestamp of review', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transmitterentry',
|
||||
name='reviewer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reviewed_transmitters', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='created',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Timestamp of creation/edit'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitterentry',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_transmitters', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='transmitterentry',
|
||||
unique_together={('uuid', 'reviewed')},
|
||||
),
|
||||
migrations.RunPython(add_review_details, remove_review_details),
|
||||
]
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-10 22:14
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0037_add_reviewer_and_date_fields_on_transmitter_entry_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='transmitterentry',
|
||||
name='is_reviewed',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.1.5 on 2021-04-13 23:10
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0038_remove_is_reviewed_field_from_transmitter_entry'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='Satellite',
|
||||
new_name='SatelliteEntry',
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='satelliteentry',
|
||||
options={'ordering': ['norad_cat_id'], 'verbose_name_plural': 'Satellite Entries'},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,108 @@
|
|||
# Generated by Django 3.1.8 on 2021-04-15 01:56
|
||||
|
||||
import db.base.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
def initialize_satellites(apps, schema_editor):
|
||||
SatelliteEntry = apps.get_model('base', 'SatelliteEntry')
|
||||
SatelliteIdentifier = apps.get_model('base', 'SatelliteIdentifier')
|
||||
Satellite = apps.get_model('base', 'Satellite')
|
||||
satellite_entries = SatelliteEntry.objects.all()
|
||||
for satellite_entry in satellite_entries:
|
||||
satellite_identifier = SatelliteIdentifier.objects.create()
|
||||
created = django.utils.timezone.now()
|
||||
satellite_entry.satellite_identifier = satellite_identifier
|
||||
satellite_entry.created = created
|
||||
satellite_entry.reviewed = created
|
||||
satellite_entry.approved = True
|
||||
satellite_entry.save()
|
||||
Satellite.objects.create(satellite_identifier=satellite_identifier, satellite_entry=satellite_entry)
|
||||
|
||||
|
||||
def reverse_initialize_satellites(apps, schema_editor):
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('base', '0039_rename_transmitter_satellite_field_to_satellite_entry'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SatelliteIdentifier',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sat_id', models.CharField(default=db.base.models.generate_sat_id, max_length=24, unique=True, validators=[db.base.models.validate_sat_id])),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SatelliteSuggestion',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'permissions': (('approve', 'Can approve/reject satellite suggestions'),),
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('base.satelliteentry',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satelliteentry',
|
||||
name='approved',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satelliteentry',
|
||||
name='citation',
|
||||
field=models.CharField(default='CITATION NEEDED - https://xkcd.com/285/', help_text='A reference (preferrably URL) for this entry or edit', max_length=512),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satelliteentry',
|
||||
name='created',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Timestamp of creation/edit'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satelliteentry',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_satellites', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satelliteentry',
|
||||
name='reviewed',
|
||||
field=models.DateTimeField(blank=True, help_text='Timestamp of review', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satelliteentry',
|
||||
name='reviewer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reviewed_satellites', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Satellite',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('last_modified', models.DateTimeField(auto_now=True)),
|
||||
('associated_satellite', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='associated_with', to='base.satellite')),
|
||||
('satellite_entry', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='base.satelliteentry')),
|
||||
('satellite_identifier', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='satellite', to='base.satelliteidentifier')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='satelliteentry',
|
||||
name='satellite_identifier',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='satellite_entries', to='base.satelliteidentifier'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='satelliteentry',
|
||||
unique_together={('satellite_identifier', 'reviewed')},
|
||||
),
|
||||
migrations.RunPython(initialize_satellites, reverse_initialize_satellites),
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue