1
0
Fork 0

Compare commits

...

384 Commits

Author SHA1 Message Date
Jeff Moe 5a7751ab30 Install sqlite3 too 2022-12-11 12:36:13 -07:00
Jeff Moe 8fd9db47e5 Spacecruft satnogs-db forklet README 2022-12-11 12:32:23 -07:00
Jeff Moe e6a15bf889 mv upstream README 2022-12-11 12:30:13 -07:00
Vasilis Tsiligiannis f6d1948e31 docker-compose: Bump 'mariadb' image version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2022-11-25 14:36:50 +02:00
George Sfoungaris 78c52e7ead Update LatestTleSet entries after removing TLE source from distributable ones
Fixes https://gitlab.com/librespacefoundation/satnogs/satnogs-db/-/issues/501

Signed-off-by: George Sfoungaris <sfou@libre.space>
2022-11-03 09:06:31 +00:00
Michał Drzał a94d14ba71 Exposes norad_follow_id in Satellite/Transmitter
Signed-off-by: Michał Drzał michal.drzal@gmail.com
2022-11-03 09:10:56 +01:00
George Sfoungaris aafb7a4c5e Remove duplicates from satellite search results
Fixes https://gitlab.com/librespacefoundation/satnogs/satnogs-db/-/issues/552

Signed-off-by: George Sfoungaris <sfou@libre.space>
2022-10-07 18:17:31 +03:00
George Sfoungaris 24aa35d904 Add warning when old TLE is shown
Closes https://gitlab.com/librespacefoundation/satnogs/satnogs-db/-/issues/557

Signed-off-by: George Sfoungaris <sfou@libre.space>
2022-10-07 18:17:06 +03:00
George Sfoungaris f847b783f6 Remove pop-up when submitting Satellite Edit Suggestion
Fixes https://gitlab.com/librespacefoundation/satnogs/satnogs-db/-/issues/503

Signed-off-by: George Sfoungaris <sfou@libre.space>
2022-10-06 14:03:52 +03:00
Alfredos-Panagiotis Damkalis 5eff9c7e13 Fix calculation bug in cache_statistics function
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-10-05 02:32:53 +03:00
George Sfoungaris 8dbea1c47e Hide invalid transmitters by default
Closes https://gitlab.com/librespacefoundation/satnogs/satnogs-db/-/issues/411

Signed-off-by: George Sfoungaris <sfou@libre.space>
2022-10-04 17:06:57 +03:00
George Sfoungaris 4eb3a9cce6 Remove schema field from telemetry model and API
Closes https://gitlab.com/librespacefoundation/satnogs/satnogs-db/-/issues/319

Signed-off-by: George Sfoungaris <sfou@libre.space>
2022-10-03 13:24:03 +03:00
George Sfoungaris 0e2cfdea83 Change misleading text about suggest&review on transmitter
Fixes https://gitlab.com/librespacefoundation/satnogs/satnogs-db/-/issues/482

Signed-off-by: George Sfoungaris <sfou@libre.space>
2022-09-30 16:48:46 +03:00
George Sfoungaris ae5bbb962d Fix typo in satelite.html template
Fixes https://gitlab.com/librespacefoundation/satnogs/satnogs-db/-/issues/529

Signed-off-by: George Sfoungaris <sfou@libre.space>
2022-09-28 13:51:09 +03:00
Alfredos-Panagiotis Damkalis 04b0605e09 Fix parsing of transmitter frequencies in javascript
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-07-28 04:01:11 +03:00
Vasilis Tsiligiannis fb4a514693 Bump 'versioneer' version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2022-07-15 14:42:53 +03:00
Alfredos-Panagiotis Damkalis 0188846f0f Add TLE updated field in satellite view
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-07-08 17:59:21 +03:00
Alfredos-Panagiotis Damkalis 97dc2b8d79 Update UI/UX for norad_follow_id
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-07-08 17:00:20 +03:00
Alfredos-Panagiotis Damkalis aa45aee2a3 Allow satellite search with norad_follow_id
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-07-08 16:43:25 +03:00
Alfredos-Panagiotis Damkalis 4e3e6c5902 Update python-satellitetle library
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-07-08 16:38:17 +03:00
Alfredos-Panagiotis Damkalis 7f1911082f Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-07-08 16:33:26 +03:00
Alfredos-Panagiotis Damkalis 56099d7055 Expose frequency violation in Satellite API endpoint
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-06-15 20:20:42 +03:00
Alfredos-Panagiotis Damkalis 03309529a2 Limit requests for violator satellites on Telemetry API
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-06-14 05:30:57 +03:00
Alfredos-Panagiotis Damkalis a13e2a0392 Require NORAD ID or Satellite ID for Telemetry API
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-06-14 05:10:47 +03:00
Alfredos-Panagiotis Damkalis 3b98e66dc8 Remove export data functionality for frequency violators
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-06-14 05:10:47 +03:00
Alfredos-Panagiotis Damkalis d7df35032d Keep logs for Telemetry API endpoint requests
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-06-04 20:36:45 +03:00
Alfredos-Panagiotis Damkalis 0dc188f41b Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-06-04 20:36:45 +03:00
Alfredos-Panagiotis Damkalis 9583d20c49 Add throttling on Telemetry API endpoint
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-06-04 20:36:45 +03:00
Alfredos-Panagiotis Damkalis 54327a8763 Expose frequency violation in Transmitters API
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-26 17:22:41 +03:00
Alfredos-Panagiotis Damkalis aecfcda191 Remove empty entries from ITU Notification URLs field
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-24 11:49:51 +03:00
Alfredos-Panagiotis Damkalis 94b2594ee7 Rename ITU "coordination" to "notification"
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-24 11:08:48 +03:00
Alfredos-Panagiotis Damkalis b563760219 Remove old coordination fields of TransmitterEntry model
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-23 13:36:02 +03:00
Alfredos-Panagiotis Damkalis 8870b425f5 Perform migration of transmitter coordination data
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-23 09:37:45 +03:00
Alfredos-Panagiotis Damkalis f63ac4294c Add ITU coordination field
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-23 08:24:00 +03:00
Alfredos-Panagiotis Damkalis 3d4afd6f78 Add IARU Coordination field in TransmitterEntry model
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-23 08:24:00 +03:00
Alfredos-Panagiotis Damkalis 9d08f9d275 Fix cache timeout for statistics
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-22 09:17:31 +03:00
Alfredos-Panagiotis Damkalis 6d5f0d1ba8 Fix 500 error of stats page when cache isn't ready
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-19 14:18:47 +03:00
Vasilis Tsiligiannis 13e045164e pyproject.toml: Require 'setuptools' versions supporting PEP 517
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2022-05-09 22:42:59 +03:00
Alfredos-Panagiotis Damkalis 96ee6f2448 Fix initial installation process
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-05-06 16:50:59 +03:00
Vasilis Tsiligiannis f6829b658a docker-compose: db: Set default charset and collation
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2022-05-05 22:48:15 +03:00
Vasilis Tsiligiannis 6eda3ca996 refresh-requirements: Verify that script dependencies are installed
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2022-04-29 19:18:40 +03:00
Alfredos-Panagiotis Damkalis a4d6601fb1 Revert "Workaround for fixing pipeline errors"
This reverts commit 09bf23b376.

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-28 13:08:40 +03:00
Alfredos-Panagiotis Damkalis 842a6b3d77 Replace django-avatar with Gravatar requests
Remove the dependency on django-avatar and use simple urls pointing
to Gravatar avatar images.

This replacement will require manual changes:
1. Remove from database the django-avatar table
2. Remove images directory that hosts old avatar images

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-27 15:13:34 +03:00
Alfredos-Panagiotis Damkalis 56255a0ad5 Unpin version of django-compressor python library
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-23 16:26:50 +03:00
Alfredos-Panagiotis Damkalis cdd2cee02f Make SECURE_PROXY_SSL_HEADER setting configurable
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-22 18:18:51 +03:00
Alfredos-Panagiotis Damkalis 4117ef7fb4 Add SECURE_PROXY_SSL_HEADER setting
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-21 14:29:41 +03:00
Alfredos-Panagiotis Damkalis 6313b93b67 Fix missing CSRF_TRUSTED_ORIGINS setting
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-20 01:37:31 +03:00
Alfredos-Panagiotis Damkalis e5c0f03339 Fix missing python-jose package
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-19 23:50:46 +03:00
Alfredos-Panagiotis Damkalis 7ae859baea Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-18 11:25:19 +03:00
Alfredos-Panagiotis Damkalis da58d39357 Update to Django 4
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-16 23:19:15 +03:00
Alfredos-Panagiotis Damkalis 5e219954bb Remove squashed migrations
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-12 13:08:25 +03:00
Alfredos-Panagiotis Damkalis e0cddccb85 Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-04-09 13:40:49 +03:00
Alfredos-Panagiotis Damkalis 26677db242 Fix color of toast element for error messages
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-03-30 13:04:55 +00:00
Alfredos-Panagiotis Damkalis 09bf23b376 Workaround for fixing pipeline errors
This fixes issue 523, this workaround should be removed after
updating to python 9.

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-03-30 15:54:03 +03:00
Fabian P. Schmidt a8370bb06b docs: Remove link to broken Python API client
The API client is undocumented[1] and effectively broken.
A similar issue exists in satnogs-network[2].

Remove links to the Python API client until it was figured out how to use it.

[1]: https://gitlab.com/librespacefoundation/satnogs/satnogs-db/-/issues/509
[2]: https://gitlab.com/librespacefoundation/satnogs/satnogs-network/-/issues/824

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2022-02-16 12:04:48 +01:00
Alfredos-Panagiotis Damkalis 2e13954fef Increase cache time and update interval for stats
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-02-07 16:27:43 +02:00
Alfredos-Panagiotis Damkalis 1eb1526cd5 Change choices of coordination field of TransmitterEntry model
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-01-11 08:52:49 +00:00
Vasilis Tsiligiannis ce07c90248 gitlab-ci: Pin OpenAPI generator Docker image
Required to ensure reproducibility

Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2022-01-11 10:21:58 +02:00
Alfredos-Panagiotis Damkalis 9ff314fe88 Fix caching statistics task
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2022-01-04 22:02:27 +02:00
Alfredos-Panagiotis Damkalis 0f95cdec29 Convert set of NORAD IDs to list in update_tle_sets task
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-12-26 17:32:34 +02:00
deckbsd 52d23bc7e0 Prevent unwanted modal close
fixes #484

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2021-11-03 09:26:31 +00:00
Fabian P. Schmidt fffca10916 Rename API Key to API Token nav item
This commit is part of a series of commits fixing the usage of
API Token vs API Key, see e4100a2 for details.

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2021-11-02 13:20:20 +00:00
Fabian P. Schmidt 9f629bb715 Specify the scope of the SatNOGS DB API token in modal
Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2021-11-02 13:20:20 +00:00
Fabian P. Schmidt d674f10e70 Rename get_apikey to get_api_token
This commit is part of a series of commits fixing the usage of
API Token vs API Key.

The REST framework is using the word API Token and we already did
as well, so we should use only this word for it.

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2021-11-02 13:20:20 +00:00
deckbsd 51d9a3f499 Check for bad transmitter only on valid ones
fixes #479

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2021-11-02 13:14:22 +00:00
deckbsd 8b641a89dc Review the wording used for suggesting a new satelitte
fixes #476

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2021-11-02 13:07:14 +00:00
Fabian P. Schmidt ba950b6a34 Fix uploads for version 2 artifacts
Fixes #497.

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2021-10-28 20:54:23 +03:00
deckbsd 24521a7f98 add sat_id filters on the different api views
fixes #473

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2021-10-25 17:48:52 +00:00
Fabian P. Schmidt 49ee559251 Enable CORS headers for artifact media file requests
Fixes #495.
Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2021-10-21 20:09:27 +03:00
Fabian P. Schmidt 37daecf507 Add support for satnogs artifacts version 2
In SatNOGS artifacts format version 2 the observation id was moved
from its own hdf5 field into a newly created metadata field.

This commit adds support for reading this field so db can handle
version 1 and version 2 artifact files now.

Fixes #493.

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2021-10-21 00:43:49 +03:00
Fabian P. Schmidt 52147ade28 Allow unauthenticated OPTIONS for artifacts API requests
CORS preflight uses unauthenticated OPTIONS requests to check for
the CORS headers. Thus they shouldn't be blocked.

This is a workaround proposed in:
https://github.com/encode/django-rest-framework/issues/5616

Fixes #491.

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2021-10-20 08:01:18 +00:00
Vasilis Tsiligiannis 26c8f4d15f refresh-requirements: Exclude both hyphen and underscore from 'pkg-resources'
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-10-20 00:15:51 +03:00
Alfredos-Panagiotis Damkalis 283cca5e40 Add CORS headers for artifacts API requests
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-10-19 03:30:34 +03:00
Alfredos-Panagiotis Damkalis a175b59f1c Return error for sids frame without Norad ID
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-10-19 03:30:34 +03:00
Alfredos-Panagiotis Damkalis 69f4938908 Install django-cors-headers python library
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-10-19 03:30:34 +03:00
Alfredos-Panagiotis Damkalis 84f754dc62 Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-10-19 03:30:34 +03:00
Alfredos-Panagiotis Damkalis 934a8eeccb Fix transmitter API test
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-08-19 12:31:36 +03:00
Papadeas Pierros 51ec39a333 Exclude invalid transmitters from api and UI.
Signed-off-by: Papadeas Pierros <pierros@papadeas.gr>

Signed-off-by: Papadeas Pierros <pierros@papadeas.gr>
2021-08-19 11:01:41 +03:00
Alfredos-Panagiotis Damkalis 34f8c4e919 Fix SatelliteCreateView when form has no NORAD ID
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-07-30 18:24:12 +03:00
Alfredos-Panagiotis Damkalis 49eb53be30 Use bootstrap-modal-form for merging satellites
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-07-26 22:18:55 +03:00
Alfredos-Panagiotis Damkalis c8081962ae Fix update_tle_sets task
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-07-23 15:35:22 +03:00
Alfredos-Panagiotis Damkalis 6341c26783 Fix decoding data task
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-07-22 11:37:44 +03:00
Alfredos-Panagiotis Damkalis 3c1eda6c2e Improve SQL queries in Satellite properties
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-07-22 11:37:44 +03:00
Alfredos-Panagiotis Damkalis e7b0e56a17 Improve performance in home view by reducing sql queries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-07-21 21:32:23 +03:00
Alfredos-Panagiotis Damkalis 3391fa8604 Fix cache_statistics function
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-07-21 21:32:23 +03:00
Papadeas Pierros f8ca235faa Add Satellite ID on UI elements.
Signed-off-by: Papadeas Pierros <pierros@papadeas.gr>
2021-07-20 12:22:47 +03:00
Alfredos-Panagiotis Damkalis 0d137863b3 Add merge satellites functionality
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-07-19 14:39:55 +03:00
Alfredos-Panagiotis Damkalis 06e2f032fe Fix edit buttons on satellites page
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-07-08 19:15:42 +03:00
Alfredos-Panagiotis Damkalis 1513e998bb Fix modal submit button functionality
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-06-23 14:58:49 +03:00
Patrick Dohmen 2081519af8 Fix Security Scanning setup
According to [1],

> Secret Detection jobs `secret_detection_default_branch` and
> `secret_detection` were consolidated into one job,
> `secret_detection`."

so the setup in `.gitlab-ci.yaml` for `secret_detection_default_branch`
is obsolete.

[1]: https://docs.gitlab.com/ee/user/application_security/secret_detection/#configuration

Signed-off-by: Patrick Dohmen <dl4pd@darc.de>
2021-06-20 15:12:13 +00:00
Corey Shields 79ca1834c9 More improved unit testing
Additional pytests, including tests for a fully populated DB

Also:

- simplify a conditional statement in the home page view

- fix broken robots.txt url parsing

- fix case in cached stats generation where new satellite id association could
trip a comparison against a NoneType by adding a default

- removed a print statement leftover from satellite id development

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-05-25 16:38:02 -04:00
Vasilis Tsiligiannis 33357e9207 gitlab-ci: Bump Docker image
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-05-24 17:23:44 +03:00
Alfredos-Panagiotis Damkalis f046863123 Fix export frames functionality
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-24 05:43:11 +03:00
Corey Shields 37b83bfa66 Improved unit testing around API calls
also adds some known-bad tests for tlm submission to ensure we handle correctly

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-05-22 09:55:32 -04:00
Alfredos-Panagiotis Damkalis d25436d594 Add JSON-LD post API endpoint for Satellite suggestions
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-20 16:59:14 +03:00
Alfredos-Panagiotis Damkalis 804b69e442 Add JSON-LD post API endpoint for Transmitter suggestions
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-20 16:59:14 +03:00
Corey Shields 4141e275cc Improve testing and handling of telemetry upload API
Improved handling of input data for the telemetry API and added tests for such

ensure that we return a 400 on an empty frame, station, timestamp

return 400 when lat/lng value conversion fails or when the resulting lat/lng is
out of range.

Fixes #463

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-05-16 18:54:48 -04:00
Alfredos-Panagiotis Damkalis 286c744141 Fix decode_all_data Satellite query
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-16 00:36:45 +03:00
Corey Shields 76c0d8f7b9 Fix workdir flag for celery with 5.0 change
Move workdir to global flag per https://docs.celeryproject.org/en/stable/whatsnew-5.0.html?highlight=workdir#new-command-line-interface

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-05-15 14:29:48 -04:00
Corey Shields ad9f4f0d6f Roll back eventlet and gunicorn for a bug
Rolling these back to avoid https://github.com/benoitc/gunicorn/pull/2581

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-05-14 21:00:16 -04:00
Corey Shields 292f1978db Remove old unused auth0login app
Back in d153ece we switched to the upstream backend social_core.backends.auth0.Auth0OAuth2

this commit removes the custom app we had to use for Auth0

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-05-14 20:40:03 -04:00
Corey Shields cbb169e5de package and dependency updates
Upgrade to Django 3.2 LTS

add DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' for Django 3.2

un-pin urllib3 and requests as the newer releases work with auth0 now

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-05-14 20:24:23 -04:00
Corey Shields c217f66b2d better error handling around latest_data
In satellite_card.html we assume if telemetry_data_count returns a value then latest_data will also run fine. This adds some better handling in the off chance that it does not (like in an odd cache state)

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-05-14 16:11:52 -04:00
Corey Shields 33dd436cc1 do not require a norad id on new satellites 2021-05-14 14:26:36 -04:00
Alfredos-Panagiotis Damkalis 3b05184d14 Add review functionality for satellite suggestions
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-11 20:27:05 +03:00
Alfredos-Panagiotis Damkalis 2f11e6b8e8 Separate aprrove permission for satellite and transmitter
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-11 20:27:05 +03:00
Alfredos-Panagiotis Damkalis 418fe5ab3d Support suggestions for creating/editing satellite
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-11 20:27:05 +03:00
Alfredos-Panagiotis Damkalis c75b491b9b Replace SatelliteEntry with Satellite in models
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-11 20:27:05 +03:00
Alfredos-Panagiotis Damkalis f4ea975e0c Remove dead code
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-11 20:27:05 +03:00
Alfredos-Panagiotis Damkalis af2d04a33f Use Satellite model for satellite API endpoint
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-05-11 20:26:52 +03:00
Alfredos-Panagiotis Damkalis d1eab762fa Update satellite models introducing Satellite Identifier
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-04-29 08:35:49 +03:00
Alfredos-Panagiotis Damkalis 0d81ecbe77 Rename Satellite model to SatelliteEntry
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-04-29 08:35:49 +03:00
Alfredos-Panagiotis Damkalis ce6a5ebf4c Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-04-29 08:35:49 +03:00
Vasilis Tsiligiannis 79ebbe219e Bump copyright date
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-04-26 13:37:01 +03:00
Corey Shields 967c05b212 update refresh-requirements
fixes deprecation warning and upcoming error:

WARNING: --use-feature=2020-resolver no longer has any effect, since it is now the default dependency resolver in pip. This will become an error in pip 21.0.

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-04-24 20:48:11 -04:00
Alfredos-Panagiotis Damkalis cc073daf45 Fix TypeError for norad_id
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-03-11 23:47:45 +02:00
Alfredos-Panagiotis Damkalis 29c0d3654b Make TransmitterEntry and its proxies readonly in admin
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-03-11 12:14:54 +02:00
Alfredos-Panagiotis Damkalis 4ea55fa1cc Fix error when there are no TLE for new satellite
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-03-11 08:29:38 +02:00
Alfredos-Panagiotis Damkalis f6fd93f53e Replace print with logging
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-03-11 08:29:38 +02:00
Alfredos-Panagiotis Damkalis 5426167d5b Remove is_reviewed field from TransmitterEntry
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-03-11 08:29:38 +02:00
Alfredos-Panagiotis Damkalis a97f9666db Add reviewer and reviewed fields in TransmitterEntry
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-03-11 08:29:38 +02:00
Alfredos-Panagiotis Damkalis 2805acde27 Rename field reviewed on transmitterentry to is_reviewed
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-03-07 19:33:59 +02:00
Alfredos-Panagiotis Damkalis 00a91ba947 Rename field user on transmitterentry to created_by
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-03-07 19:01:34 +02:00
Alfredos-Panagiotis Damkalis efd5dd1a96 Add setting for enabling ZeroMQ feature
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-02-18 18:04:07 +02:00
Corey Shields 2b17d32a67 Replace gpredict.js with satellite.js
package.json: Add satellite.js dependency

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>

Move to satellite.js in map, drop satellite footprint

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>

Remove gpredict.js dependency

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>

Replace gpredict.js with satellite.js

Builds on the work of kerel-fs in !596 and #440, implements satellite.js for core TLE and SGP4 handling, deprecating and removing gpredict.js

fixes #440
does NOT fix #204
 
Signed-off-by: Corey Shields <cshields@gmail.com>
2021-02-13 14:56:14 -05:00
Alfredos-Panagiotis Damkalis 8adda93204 Add station_id field to published DemodData
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-02-04 12:55:35 +02:00
Alfredos-Panagiotis Damkalis afda478ccc Add station_id in DemodData model
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-02-04 12:16:16 +02:00
Alfredos-Panagiotis Damkalis 443668b90c Add observation_id field for published DemodData
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-01-30 19:51:11 +02:00
Alfredos-Panagiotis Damkalis 2b3282792d Add observation_id in DemodData model
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-01-30 19:38:53 +02:00
Alfredos-Panagiotis Damkalis 8ca9004fc9 Change ZEROMQ_SOCKET_URI to 127.0.0.1
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-01-20 13:40:50 +02:00
Alfredos-Panagiotis Damkalis 0e6c120cc9 Narrow ignored members to zmq library
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-01-20 13:09:00 +02:00
Alfredos-Panagiotis Damkalis 4aed383ed0 Add observer field to ZeroMQ sent data
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-01-20 13:06:29 +02:00
Alfredos-Panagiotis Damkalis 87ac2f9dc9 Remove post_save signal for Telemetry object
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-01-20 13:06:29 +02:00
Alfredos-Panagiotis Damkalis 4b8239739a Publish new SiDS frames by using a ZeroMQ socket
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-01-20 13:06:29 +02:00
Alfredos-Panagiotis Damkalis 1b366d1722 Add zeromq python library
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-01-20 13:06:29 +02:00
Alfredos-Panagiotis Damkalis f1d885f172 Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2021-01-20 13:06:29 +02:00
Corey Shields 131831a811 Add version field for demoddata entries
Adds an optional 'version' field which will store version details for clients that are sending it (such as some SiDS clients, even though version is not clearly indicated in the protocol pdf)

I set this as a 45 length CharField, anticipating that there may be some application names, versions, and possible git hashes included as well.

Adds a db migration for this field, blank by default.
 
also removed an unnecessary setting which was default (see !628)

Relates to #456

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-01-19 19:38:54 -05:00
Corey Shields bb24e771d6 minor updates to satnogs-db-api-client
With the new drf-spectacular generated schema, need to add nulltype to the list of requirements to install

update copyright dates

Improve examples and client generated docs via drf-spectacular

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-01-17 11:45:52 -05:00
Vasilis Tsiligiannis 0f09f58629 Fix skipping of migrations by 'isort'
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-01-14 16:04:08 +02:00
Vasilis Tsiligiannis fd337e9dd0 Remove futile dependency conflict workaround
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-01-14 15:51:05 +02:00
Vasilis Tsiligiannis 8107ee4460 gitlab-ci: Bump 'tox' version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-01-14 15:50:35 +02:00
Vasilis Tsiligiannis 98acbd2640 tox: Bump 'twine' and 'sphinx_rtd_theme' versions
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-01-14 15:28:20 +02:00
Vasilis Tsiligiannis 79382a5c2f tox: Bump 'pylint' and 'pylint-django' versions
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-01-14 15:28:20 +02:00
Vasilis Tsiligiannis d92e655cb5 Use Python 3 style 'super()'
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-01-14 15:28:20 +02:00
Vasilis Tsiligiannis 648db98037 tox: Bump 'yapf' version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-01-14 15:28:15 +02:00
Vasilis Tsiligiannis a114826f80 tox: Bump 'isort' version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-01-14 14:45:25 +02:00
Vasilis Tsiligiannis 3d96fe1e41 Fix YAPF dict formatting
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2021-01-13 20:21:55 +02:00
Corey Shields 5e03f7c759 Epic API (doc) changes for SatNOGS DB
I've decided to change things up in API schema and doc generation.

Work is not quite complete but its enough for testing in dev and feedback.

Major changes:
* Renaming of api.view classes to match ViewSet inheritance (minor annoyance)
* Introduce drf-spectacular for schema generation and doc UI via swagger-ui
* lots of doc changes for the API to provide a good experience with the above.

New schema generation should work seamlessly in gitlab ci, as well as via /api/schema dynamically.

The new swagger ui view is available via /api/schema/docs/

Signed-off-by: Corey Shields <cshields@gmail.com>
2021-01-09 20:44:19 -05:00
Corey Shields bec7469dc2 bump container from python 3.8.6 to 3.8.7, revert gunicorn
There are a slew of issues related to eventlet monkeypatching ssl.py which leads to the issues we've seen in the updates I pushed to dev this week. The gunicorn update to 20 seems to be the culprit here, and wasn't caught locally because "develop" in djangoctl.sh uses the django runserver, not gunicorn.

Hopefully this fixes our issues, and I'll slowly update eventlet, requests, and urllib3 back up
 
Signed-off-by: Corey Shields <cshields@gmail.com>
2021-01-03 18:43:23 -05:00
Corey Shields 8ec3b93aba auth0 debugging: pull requests and urllib3 back down
The RecursionError we keep hitting is happening in requests+urllib3, and I can't see why quite yet but there are enough changes there to try rolling them back, if the problem goes away then we've found an upstream bug.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-30 19:30:39 -05:00
Corey Shields e5ddc38e96 add https back to callback url on auto0
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-30 17:53:30 -05:00
Corey Shields d153ece5ef Trying upstream auth0 backend in social_core
Trying the upstream backend against the ssl recursion issue we are seeing in dev.  May back out.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-30 17:16:21 -05:00
Corey Shields bcee356c24 auth0login updates
Start using JWT from auth0, along with updated social-auth-app plugin and newer jose.  Tested locally but only with a http redirect, will test ssl in dev

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-30 10:29:28 -05:00
Corey Shields 46eb5aa8c8 Roll a couple of libraries back
We seemed to hit a regression in celery with db-dev that should have been fixed in https://github.com/celery/celery/issues/6445 but is causing us issues.

Also pulling the social-auth package back with a weird ssl recursion issue.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-29 22:04:24 -05:00
Corey Shields 17f4ab100b Update packages and libraries
Updating pretty much everything but django itself, ahead of a jump to Django 3 real soon.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-29 22:46:51 +00:00
Corey Shields a1aafce76b Offer a dynamic API schema view
adds the /api-schema URL which will provide the current API schema in OpenAPI format, both in rendered and downloadable formats.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-29 15:00:08 -05:00
Corey Shields 0fc7107172 Tighter exception handling
Removed most of the bare Exception catches and put proper errors to catch in their place.

Reworked a bit of the gridsquare calculation, any exceptions raised should now be handled within the function.

I am purposefully leaving the 2 W0703's we have for data demodulation alone for now, as that will be quite a rabbit hole. Otherwise,

fixes #316

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-29 14:13:12 -05:00
Corey Shields 46883750e4 Remove former openapi postprocess script
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-29 10:33:16 -05:00
Vasilis Tsiligiannis 18ff90735e docs: Add requirements file
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-12-29 17:17:39 +02:00
Corey Shields 83f9b07e21 Updates for newer API / django rest framework
Update to Django Rest Framework 3.12.2 with improved schema generation.

However, it is still not quite complete for what we need (and what we currently postprocess for). Instead of postprocessing, this commit introduces our own extended generator to add the missing fields.

Once this is vetted good, we can remove contrib/postprocess-openapi-schema.py 

Also added better comments to api/views.py which will end up in schema docs

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-12-29 07:48:58 -05:00
Vasilis Tsiligiannis cc422353e7 docs: Simplify Sphinx configuration
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-12-29 13:13:57 +02:00
Vasilis Tsiligiannis 1abbf425ef gitlab-ci: Copy API documentation to pages
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-12-29 12:40:55 +02:00
Vasilis Tsiligiannis 1da488642a gitlab-ci: SAST: Do not scan dependencies
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-12-29 12:09:47 +02:00
Vasilis Tsiligiannis 7629b8f762 gitlab-ci: Fix missing assets
DAG was not set correctly and collected assets were not passed to
subsequent jobs that needed them.

Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-12-29 10:47:36 +02:00
Vasilis Tsiligiannis 9abde9b765 Add Read the Docs configuration
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-12-28 21:34:52 +02:00
Vasilis Tsiligiannis 99f3a5fcc2 Override install requirements when building with Read the Docs
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-12-28 21:34:30 +02:00
deckbsd c07696c80e fix satellite structured data
fixes #431

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-12-19 12:14:02 +00:00
Papadeas Pierros 9e8e1b169f Fix analytics code inclusion
Signed-off-by: Papadeas Pierros <pierros@papadeas.gr>
2020-12-11 13:11:03 +02:00
Vasilis Tsiligiannis 8d60968729 Fix catching of Kaitai decoding exceptions
Kaitai Struct exceptions inherit from 'BaseException' instead of
'Exception' class. This issue is fixed upstream but did not make it
to the release of Kaitai Struct (see PR #53).

Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-12-08 23:58:57 +02:00
deckbsd 3ccc3ef0fd fix wrong dates showed on last 30 days data graph
Fixes #444

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-12-03 17:29:35 +01:00
Corey Shields c7772f4c78 Show uplink mode
Show the uplink mode in the transmitter card when it exists.

Fixes #437

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-11-14 08:57:57 -05:00
Vasilis Tsiligiannis a3866f180b Bump 'mysqlclient' version, refresh requirements
This should fix failing 'Read the Docs' documentation generation since
presence of 'mysql' executable is not a checked when installing a wheel
package.

Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-11-09 18:23:34 +02:00
Vasilis Tsiligiannis fce52ec637 refresh-requirements: Use new dependency resolver
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-11-09 18:12:11 +02:00
Vasilis Tsiligiannis 1f2dc4b75f Invalidate Docker cache by setting argument
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-11-04 10:46:52 +02:00
Vasilis Tsiligiannis 6680a7fe12 gitlab-ci: Create Sentry release on each tag
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-10-31 01:22:56 +02:00
Vasilis Tsiligiannis 6da1afd742 gitlab-ci: Always use latest compatible version of 'satnogs-decoders'
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-10-31 01:21:51 +02:00
Vasilis Tsiligiannis b98cb87f4d sentry: Set release from version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-10-31 00:59:34 +02:00
Vasilis Tsiligiannis d435eef416 sentry: Set environment tag
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-10-31 00:02:04 +02:00
Vasilis Tsiligiannis 816eba90c2 gitlab-ci: Pass commit SHA or tag variable when triggering pipelines
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-10-26 20:24:36 +02:00
Patrick Dohmen 83f383d851
Bump kaitaistruct version
Signed-off-by: Patrick Dohmen <dl4pd@darc.de>
2020-10-22 17:41:32 +02:00
Corey Shields b44b618c04 set TLE object relationship to NULL when satellite is deleted
Changes CASCADE to SET_NULL when upstream satellite object is deleted, preserving the TLE history in DB. 

Tested locally with a deletion

Fixes #435

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-10-11 10:13:07 -04:00
Corey Shields 2400c6b4f6 Add sorting by status to satellites view
Adds the ability to sort on the 'status' column (which today does nothing) by setting the field by which datatables will sort on to sat.status

This also has the benefit of filtering on sat.status as well, though one would need to know the backend statuses to use (ie: 'alive' vs 'dead'). Not sure that is ideal but this is a quick fix.

Fixes #428 

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-10-11 09:51:45 -04:00
Corey Shields a653f0697c Fix export last week bug
As reported in #433 there was a bug causing export requests for the last week to be treated as a last month export.

Fixes #433 by treating the period variable as the int that it is. Also makes the preceding check a little easier to understand by being explicit against the None case. 

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-10-11 08:57:07 -04:00
Vasilis Tsiligiannis 08cd629c05 Improve reproducability of CI and image building (fixes #436)
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-10-09 12:54:16 +03:00
Fabian P. Schmidt 3ed0d750db transmitters-chart: Show satellite name in tooltip
Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2020-10-08 22:02:08 +02:00
Pierros Papadeas 2835df9553 Add transmitters chart
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-10-03 15:35:16 +03:00
Alfredos-Panagiotis Damkalis 8e52ab8e8d Update last_modified field on LatestTleSet object update
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-30 09:09:58 +03:00
Vasilis Tsiligiannis f85273a592 isort: Fix line length configuration
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-29 00:19:55 +03:00
Alfredos-Panagiotis Damkalis f53d240f5b Generate observer in DemodData object in api/view
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-28 12:08:21 +03:00
Alfredos-Panagiotis Damkalis 387affac12 Fix AttributeError on decode_data function
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-28 10:06:49 +03:00
Alfredos-Panagiotis Damkalis 2b594d427c Stop triggering signals during DemodData decoding
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-28 04:23:46 +03:00
Alfredos-Panagiotis Damkalis b31d40809f Fix race condition in DemodData decoding
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-27 22:43:44 +03:00
Alfredos-Panagiotis Damkalis ed0a22503d Improve performance when update LatestTleSet entries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-26 22:55:03 +03:00
Vasilis Tsiligiannis 5406261482 Refresh requirements, fix conflicts
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-26 01:11:20 +03:00
Vasilis Tsiligiannis 8561037130 gitlab-ci: Fix disabling of Babel on SAST scanning
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 23:45:09 +03:00
Vasilis Tsiligiannis 34c7e4fd03 gitlab-ci: Improve Docker image reproducability
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 23:32:43 +03:00
Alfredos-Panagiotis Damkalis 815d56a6f8 Ignore set TLE sources from latest TLE calculations
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-25 20:34:29 +03:00
Vasilis Tsiligiannis e29e8eb5a4 gitlab-ci: Enable license scanning
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 19:43:48 +03:00
Vasilis Tsiligiannis 30e0c27461 gitlab-ci: Deploy right after Docker image is pushed
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 19:39:50 +03:00
Vasilis Tsiligiannis e6a76cf1a7 gitlab-ci: Reorder jobs in YAML file
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 19:39:19 +03:00
Vasilis Tsiligiannis 13c39be360 gitlab-ci: Split application and API client build into separate jobs
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 19:03:03 +03:00
Vasilis Tsiligiannis b0aebd41b9 gitlab-ci: Set DAG relationships
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 18:41:04 +03:00
Vasilis Tsiligiannis 9a39a70722 gitlab-ci: Remove deprecated dependency scanning option
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 18:32:25 +03:00
Vasilis Tsiligiannis 53ea7728e2 gitlab-ci: Disable Babel for NodeJS scanning
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 18:32:25 +03:00
Vasilis Tsiligiannis 6af7d06a74 gitlab-ci: Fix security templates path
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 18:32:25 +03:00
Vasilis Tsiligiannis b457e7460d gitlab-ci: Enable secret detection job
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-25 18:32:25 +03:00
Patrick Dohmen cb0b00e304
Fix wrong break statements and optimize django query
Fixes issue 427

Signed-off-by: Patrick Dohmen <dl4pd@darc.de>
2020-09-25 10:42:44 +02:00
Alfredos-Panagiotis Damkalis 8e2f52a59f Fix datetime format in Tle API endpoint
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-25 04:59:18 +03:00
Patrick Dohmen 1e9b695569 Issue #422: fix character field size
Includes migration.
Fixes issue #422

Signed-off-by: Patrick Dohmen <dl4pd@darc.de>
2020-09-23 16:55:50 +00:00
Alfredos-Panagiotis Damkalis 09e630890f Add absolute path to logo image on 5xx pages
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-23 17:04:40 +03:00
Alfredos-Panagiotis Damkalis a841bd48d5 Add static html pages for 501, 503 and 504 errors
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-23 13:33:27 +03:00
Alfredos-Panagiotis Damkalis 63e1c50f8d Add custom admin save/delete methods for Tle model
As currently the only way to add/delete manually Tle objects is
through admin panel, I've replaced Tle signal receivers with custom
admin save/delete methods in order to trigger updating of
LatestTleSet objects more efficently.

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-22 22:25:23 +03:00
Alfredos-Panagiotis Damkalis e38fa63079 Add list_filter on LatestTleSet admin view
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-22 22:18:17 +03:00
Alfredos-Panagiotis Damkalis 87d5329c36 Fix tle_source field in satellite view template
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-22 22:18:17 +03:00
Patrick Dohmen 6dd9bff360
Move signal handlers to separate files
Signed-off-by: Patrick Dohmen <dl4pd@darc.de>
2020-09-22 21:02:46 +02:00
Patrick Dohmen 2ef400acf9
Add task to decode a currently stored frame
Signed-off-by: Patrick Dohmen <dl4pd@darc.de>
2020-09-22 21:02:46 +02:00
Vasilis Tsiligiannis 31320f739c Implement script to post-process OpenAPI generated schema
The script can be used to:
- Expand aliases and anchors
- Set API version
- Set server URL
- Enable API key authentication scheme

Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-22 21:38:07 +03:00
Vasilis Tsiligiannis 2b714a754e gitlab-ci: Remove futile copy of API docs
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-22 21:38:07 +03:00
Alfredos-Panagiotis Damkalis 6a12f76a26 Remove LatestTle model and introduce LatestTleSet model
LatestTle model was a proxy model on Tle one, using it for API adds a
significant delay to respond on the API requests. For this reason
LatestTle model is removed and LatestTleSet model takes its place.

LatestTleSet is updated asynchronously and keeping references to
latest TLE sets of Tle model. This allows to retrieve the TLE sets
faster.

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-22 17:37:55 +03:00
Alfredos-Panagiotis Damkalis ae48ac7008 Move app's signals and methods to seperate file
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-22 17:23:29 +03:00
Alfredos-Panagiotis Damkalis 74fd41b2a8 Remove Tle model constaint
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-22 17:23:18 +03:00
Alfredos-Panagiotis Damkalis e27fbb09b9 Add status "future" migration
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-21 19:17:54 +03:00
Pierros Papadeas 5c01c5d72b Add satellite status 'future'
fixes issue 409

Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-09-21 18:26:13 +03:00
Vasilis Tsiligiannis b51e6f4792 docs: Properly set version and release
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-21 17:43:00 +03:00
Vasilis Tsiligiannis 498fa704a3 docs: Point to generated API documentation
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-21 17:41:05 +03:00
Vasilis Tsiligiannis e2086c72b8 satnogs-db-api-client: Generate API documentation
Generate API documentation from OpenAPI specification. Also, update
outdated Sphinx documentation and include the generated docs.

Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-21 13:13:10 +03:00
Alfredos-Panagiotis Damkalis 45eb948f57 Add button to update TLE sets in Tle admin panel
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-18 04:47:35 +03:00
Alfredos-Panagiotis Damkalis 816ab8cd49 Add 'norad_follow_id' field to Satellite model
The 'norad_follow_id' field is used for aquiring TLE sets for a
satellite with a temporary NORAD ID.

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-17 20:35:44 +03:00
Alfredos-Panagiotis Damkalis 09bb5b0875 Remove satellite TLE fields in favor of Tle model
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-15 21:05:43 +03:00
Alfredos-Panagiotis Damkalis dd10892eaf Use new model for TLE sets
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-09-15 21:05:43 +03:00
Vasilis Tsiligiannis 37cc41d86c Make file uploads world readable
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-14 22:38:18 +03:00
Corey Shields 0dbb1df42d Stats page improvements
Various improvements to the stats page:

Added a legend of top chart items to the footer of bands and modes charts. Fixes #408

Added a (cached) count of decoded frames to the Satellites table. Fixes #400

Added a note about the cached nature of stats. Fixes #403

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-12 17:10:06 -04:00
Corey Shields 2e25763185 Make contributor list scrollable
Hooray for success - we have tons of contributors in a 24 hr leaderboard!! Making this fit the screen a bit better and scrollable.

Fixes #402

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-12 14:33:45 -04:00
Corey Shields b5dd2eb4ea Fix TLE formatting in Satellite Data tab
Have to make this card a full width, even at col-xl-6 size it can shrink to stretch past the edge of the card. Adjusted a little more to make better visual use of the space as well.

Fixes #426

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-12 14:03:45 -04:00
Corey Shields 3af17917ba docstring fix
small docstring fix, we are not moving toward any 'purchases' :)

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-12 13:16:52 -04:00
Corey Shields eb859b7a44 show status text if satellite has uncoordinated transmitter
If the satellite has a transmitter that is flagged as 'bad_transmitter' (frequency coordination violation today), then show such in the Status card.

Fixes #250
 
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-12 11:00:50 -04:00
Pierros Papadeas fa32a0ebb4 Fix broken links to Dashboards and Network Observations
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-09-09 12:13:25 +03:00
Corey Shields d6f6ff0c2e Accessibility improvements
Some accessibility improvements:

* fixed some buttons to be more compatible with keyboard focus
* added labels where missing for tab links where a screenreader might get confused by the fontawesome icon
* added a "Skip to main content" link
* labeled the search box 
* added labels for some of our visual indicators for satellite and transmitter statuses
* ensured that "Focusable elements should have interactive semantics"

Fixes #414
Fixes #417
Fixes #418
Fixes #419
Fixes #420

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-07 12:38:59 -04:00
Vasilis Tsiligiannis cb705d3c1b Add schema YAML aliases workaround
This patch is a workaround to https://github.com/encode/django-rest-framework/issues/7479

Load and dump YAML using a dumper with disabled aliases in order to
workaround the aforementioned bug.

Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-07 19:17:25 +03:00
Vasilis Tsiligiannis 613ef7cdbb Refresh requirements
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-07 19:17:17 +03:00
Vasilis Tsiligiannis db9457776c Update Django REST Framework
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-09-07 16:44:51 +03:00
Corey Shields 3d1c504e1d Linting and cleanup of unused view data
Linting for imported-auth-user to use get_user_model() instead

Pulled unused (old) variables and queries out of views for home, satellites, satellite, stats. Should improve page load a bit.

Relates to #424 

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-06 13:24:15 -04:00
Corey Shields 402ec942ab Add frequency coordination status to transmitters
Adds 2 fields to transmitter model, coordination (fixed set of choices, defaulting to blank) and coordination_url. API, UI, and tests are included.

Also introduces a bad_transmitter property to transmitter that will return true if any 'Uncoordinated' or '..Rejected' status is selected.

Fixes #311

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-06 11:32:15 -04:00
Corey Shields 0600d2a090 Change the way admin notifications are triggered for new transmitters
Changes the admin email notifications to a backend celery task so that the UI does not block on it.

Also prevents this process from running twice as form_valid gets triggered for validation and saving.

Should fix #421

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-03 14:14:44 -04:00
Corey Shields b002ca6e37 improve deep link anchor formatting
Be more consistent with /url#anchor when deep linking to tabs in satellite and stat views
 
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-09-03 10:01:01 -04:00
Corey Shields b7e82848d5 Add ability to link to tabbed panes and fix map visibility
handle deep-linking of anchors into tabbed panes on the satellite and stats pages.
Fixes #399

Also clean up map visibility, only showing if a satellite has not re-entered *and* has a TLE associated with it (note, we are not testing for validity of the TLE).
Fixes #412
 
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-30 16:17:08 -04:00
Corey Shields 5395122ac7 Fix for satellite image upload in new UI
Image files were not getting uploaded due to the lack of enctype on the form.

Easy fix, hard discovery (sigh)

Fixes #413
  
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-27 21:00:42 -04:00
Corey Shields aa5eaaad74 Better numerical sorting for transmitters table
Improve the column sorting for numerical data per datatables.net docs at https://datatables.net/plug-ins/sorting/natural

Fixes #407
 
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-27 20:12:17 -04:00
Pierros Papadeas 87ab59c738 Fix datetime format displaying
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-08-14 16:33:50 +03:00
Alfredos-Panagiotis Damkalis f18140e153 Add distribution logic on TLE model and API endpoint
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-08-11 15:11:51 +03:00
Alfredos-Panagiotis Damkalis e1b859887f Add API endpoint for TLE
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-08-11 15:11:51 +03:00
Alfredos-Panagiotis Damkalis e2b916f3ed Add task for fetching and saving TLE sets
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-08-11 15:11:51 +03:00
Alfredos-Panagiotis Damkalis ce1c189f02 Add Tle and LatestTle models
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-08-11 15:11:51 +03:00
Corey Shields fda7defc8b Pull dnt-helper from dependencies
We were not implementing dnt-helper, instead should use django-dnt.

Relates to #330
Relates to #406
2020-08-09 13:41:17 +00:00
Alfredos-Panagiotis Damkalis 00e9cb1cc3 Fix update transmitter bug and new suggestion email
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-08-09 16:04:05 +03:00
Corey Shields 75ad7ecd35 Update api client six version to avoid conflict with parent's req's
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-08 20:52:46 -04:00
Alfredos-Panagiotis Damkalis 2c97ff4f58 Use cache for satellite property "telemetry_data_count"
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-08-09 02:28:04 +03:00
Alfredos-Panagiotis Damkalis 41cb47f9ff Improve performance and reduce SQL queries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-08-09 01:05:01 +03:00
Corey Shields 771aa22ecd Some more performance improvements for home page
Add index to DemodData.timestamp

prefetch suggestions

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-08 13:30:44 -04:00
Corey Shields 931e0893d5 change latest demod query to pk instead of timestamp
should help query load, pk is indexed.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-08 10:44:52 -04:00
Corey Shields 567df49a8f quick fix for prod stats page, account for blank noradid
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-08 09:47:46 -04:00
Corey Shields 46c73722c4 change status look and feel to match rest of satellite page
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-07 19:39:12 -04:00
Corey Shields 07a4904ff8 Move recent_decoded_cnt to ajax view
Move the new recent_decoded_cnt out of api and into the base view

fixes #398

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-06 18:14:08 -04:00
Pierros Papadeas 1fd8406ab5 Fix minor spelling mistake
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-08-05 14:15:29 +00:00
Pierros Papadeas 01c581ea8a Change satellite table wordings
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-08-05 13:11:42 +00:00
Alfredos-Panagiotis Damkalis ed6ac366fb Change TLE panel in satellite page
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-08-05 11:59:27 +03:00
Corey Shields f6d2bcdc93 Fix control-sidebar toggling
Fix a regression in control-sidebar toggling

Sets a fixed top navbar for all window sizes

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-04 21:28:36 -04:00
Corey Shields 44daf627be control-sidebar fixes and help html formatting
Fix the toggling of the control-sidebar, the text coloring of the control-sidebar, and clean up html on the help page.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-04 19:09:07 -04:00
Corey Shields 8b90fd1ff9 Fill in help page and tweak about page
Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-04 18:42:51 -04:00
Corey Shields f05658476f Django 2.2.15 upgrade
Quick version bump before prod release

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-04 17:05:40 -04:00
Corey Shields 3149c91e56 Add decoded data recent history chart to satellite view
Adds a chart card showing the last month of decoded data as currently stored in influxdb.

Unfortunately there's no native way to count the number of 'entries' for a measurement across a given time, so we have to do a count(*) across all points. This returns a blob of <timestamp>, <count(pointa)>, <count(pointb)>, <...> and in almost (but not all) cases those counts will be identical but we have to account for it not, so I iterate over each timestamp (client side) and take the max count to assign to that point in time. Since we are doing a 30d query from influx with 1d aggregation this should not be too intensive.

Also cleaned up the coloring of the profile link buttons.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-04 08:33:22 -04:00
Corey Shields b198e188d3 Add text to search not found
Fixes #371

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-02 16:11:36 -04:00
Pierros Papadeas 9ad52c8da8 Fix logo on nav bar mobile and titles
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-08-02 22:13:10 +03:00
Corey Shields 488fd9d7b4 Add migration for model validators and help_text
Fixes #397

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-02 14:07:19 -04:00
Corey Shields 5d0df9ac8f Update satellite data panel to match info panel cards
Update the card look and feel from the Data panel to match everything else in the Info/Profile panel.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-02 13:41:33 -04:00
Corey Shields 2f51df5337 Adjust top navbar for mobile
Make adjustments to the responsiveness of the app, especially for mobile navigation.

Fixes #391

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-02 13:25:47 -04:00
Pierros Papadeas 657260ed9f Add satellite status info box
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-08-02 17:56:54 +03:00
deckbsd 72b1a4abad enlarge mapbox on a sat page
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-08-02 15:13:03 +02:00
Pierros Papadeas 86d1bd6abc Group satellite profile items in cards
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-08-02 12:36:16 +00:00
Pierros Papadeas 96e971d614 Remove unused CSS file and reference
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-08-02 12:36:16 +00:00
deckbsd 01d9602693 fix map button remains active issue
Fixes #396

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-08-02 09:35:02 +02:00
Alfredos-Panagiotis Damkalis 7938239120 Remove python 2 combatibility code
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-08-02 09:05:41 +03:00
Corey Shields 0ba46f0913 More transmitter submission improvements
Relates to #233
Fixes #364

More improvements to the transmitter submission process to auto-convert PPB to Hz and back, as well as popover.js tooltips from help_text in the model.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-01 23:08:13 -04:00
Corey Shields 2b80a3a88f make transmitter creation and edit forms dyanmic based on type
Re-implementing the case-on-type of the prior db UI.

To keep CSP happy along with the fact that we dynamically load the modal with django-bootstrap-modal, the javascript for these modals is in a new file, and that file's hash is kept in the CSP.

Also re-introduces child_src to test fixing a safari mapbox bug

Relates to #233
Fixes #387
2020-08-01 18:50:17 -04:00
Corey Shields b41b77be94 Remove news link (for now)
Pushing off the idea of SatNOGS DB having a 'news' component to > 1.0, removing this link for now.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-08-01 10:59:54 -04:00
deckbsd 768779ec6a add stage notice for -dev #392
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-08-01 14:44:48 +00:00
deckbsd ee7c8fdcef fix dashboard link on sat page
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-08-01 14:31:27 +00:00
Pierros Papadeas c444bf3645 Cleanup unused images and html
Fix #393

Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-08-01 11:06:29 +03:00
deckbsd 461c8af026 include search as initial data in search box
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-08-01 07:23:17 +00:00
Corey Shields c1d57a79cb Fix recent data submitters in satellite page
The intent of the "5 recent submitters" on the satellite page is to show the 5 most recent stations and their last submitted time, whereas this was showing the last 5 submissions regardless of the ground station.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-07-31 20:52:14 -04:00
Corey Shields 95e89dc4b5 Fix up js dependencies
Use upstream admin-lte 'plugins' instead of managing these js/css dependencies ourselves.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-07-31 23:17:11 +00:00
Corey Shields 508a22acf0 Show transmitter submission only for logged in users
Fixes regression where 'submit transmitter' link and modal were visible and partly accessible when not authenticated. Also tightens the satellite edit modal similarly.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-07-31 17:38:07 -04:00
Pierros Papadeas c17c7104f8 Replace SatNOGS DB logo
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-07-31 21:38:36 +03:00
Pierros Papadeas a1fe08f352 Remove uneeded JS
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-07-31 10:02:26 +03:00
Corey Shields ff3d88001e Refacter transmitter_card fields and kaitai struct decoder info
Removes the field.html include - and the frequent inclusion of that in the transmitter cards.

Fix a bug in satellite.js where input not recognized as an integer

Add gitlab links to the version info

Removes the manually-entered kaitai struct field from view (will need removed from db in a later change) - replacing with a procedurally generated list of fields from the class imported through satnogsdecoders.

Fixes #385

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-07-30 18:51:48 -04:00
deckbsd faa38e3960 add unit on frequency fields on transmitter modal
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-30 10:54:23 +00:00
deckbsd f9baf45aa1 Add checks for high and low freq on transmitter model
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-30 07:31:01 +00:00
Pierros Papadeas 1f6f74be64 Consolidate all info in About page
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-07-30 00:33:10 +03:00
deckbsd ead89b6d03 add model validators
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-29 13:12:54 +00:00
deckbsd f0bee6eda9 fix exception when accessing search page without q parameter
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-29 12:01:23 +00:00
deckbsd 60f2356c73 migrate url uses to path
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-29 06:25:27 +00:00
Corey Shields b7d2392c35 Use datatables for stats page and other tweaks
Implement datatables for ground stations and satellites stat pages.

Fix ground stations icon

Improve datatables footer spacings all around

Tweak spacing around gravatar

Fixes #380

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-07-28 20:32:07 -04:00
Corey Shields 4c3147d4c5 Fix transmitter card titles for long titles
Fix the case where a long transmitter title will push the icons down into a wrapped row.

Fixes #271
2020-07-28 19:29:15 -04:00
deckbsd 05d4f8a705 make large badge number human readable
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-28 10:06:29 +02:00
Corey Shields cbd0ec096a Bring back transmitter uuid
Bring transmitter uuid and copy-to-clipboard back to new UI.

Also fixes tooltips in new UI

Also fixes a typo in new 404 screen.

Fixes #382

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-07-26 21:21:36 -04:00
Corey Shields 122baae403 Improved error pages
Improves the look and feel of 404/500 pages to match with BS4/LTE look and feel.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-07-26 19:09:10 -04:00
Corey Shields 68e1e20a4e New UI fixes: data export, transmitters count, sat data leaderboard
Fixes #384 - by cleaning up the data export links

Fixes #379 - simple fontawesome change

Fixes #308 - adds 'most recent 5' observation submitters (regardless of number of submissions or staggering of submissions)

Changes the queryset for satellites to pull the properly 'approved'/valid transmitters as approved_transmitters which can be used to count transmitters with |length and reduce queries at the same time. As such:

Fixes #365
Fixes #381

Thanks to @deckbsd and @adamkalis for their assistance on these!

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-07-26 17:21:05 -04:00
deckbsd 999bab50c6 add the new sat fields trough the sat api endpoint
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-26 16:28:49 +02:00
Corey Shields a7141c5b30 New SatNOGS DB user interface
Initial commit of new UI. There is still some work to be done before this goes into dev, but here is the work so far:

* Updated dependencies to latest 2.x django
* Updated to Bootstrap 4
* New home screen to display most recent satellite entries, most recent data, and contributors
* Adopted django-bootstrap-modal-forms for handling satellite and transmitter creation and update, with more of an emphasis on django's model/view/form model - and a dynamic flow where the modals and details are only loaded when the proper icon is clicked, reducing the overall page size
* Adopted AdminLTE 3.x framework atop Bootstrap 4
* Created reusable cards for satellite and transmitters
* Cards and Modals are organized into subdirectories for template includes and base templates, respectively
* New stats display widgets using BS4 and AdminLTE 3
* Satellite search is redesigned and now accessible from any page of the site
* Introduced datatables for an "All Satellites" view and a modification of the new "All Transmitters" view
* Focus on all UI scaling down to mobile devices
* New model created for Operator (/ Owner): name, names, description, website
* Added django-countries for support of CountryField
* Satellite model expanded to include: Operator, (satellite) website, countries, launched datetime, deployed datetime
* Transmitter suggestions can now be approved in the UI by superusers
* Satellite entries can now be edited in the UI by users with the change satellite permission
* Satellite page is now broken into 'tabbed' panels (Profile, Map, Transmitters, etc) - with the tab menu options appearing in the sidebar or at the top depending on screen size
* Other cleanup and changes that I'm missing for sure.

Signed-off-by: Corey Shields <cshields@gmail.com>
2020-07-25 22:08:44 +00:00
deckbsd 63a93fa1ed change used sat id for caching from norad to pk
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-25 17:21:52 +00:00
deckbsd 5122729752 create one stats cache per satellites
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>

replace the for loop by one big query

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>

Undo the modification made for test

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-25 17:21:52 +00:00
deckbsd 317eed5a34 fix bad login redirection on new trans modal
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-07-19 07:20:51 +00:00
Alfredos-Panagiotis Damkalis 8c319bfe62 Add browsable API for jsonld format
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-16 06:26:23 +03:00
Alfredos-Panagiotis Damkalis f5e61c0124 Fix broken jsonld endpoint on unauthenticated request
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-16 06:25:47 +03:00
Alfredos-Panagiotis Damkalis 504392d1e7 Allow access to API endpoints for logged in users
API endpoints that are behind authentication check, are now
accessible by logged in users.

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-16 06:11:09 +03:00
Alfredos-Panagiotis Damkalis 6094847432 Fix python warnings
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-13 13:32:45 +03:00
Alfredos-Panagiotis Damkalis aef8d5c33b Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-13 13:32:45 +03:00
Alfredos-Panagiotis Damkalis 118cb7cadc Remove python 2 compatibility code
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-13 13:32:45 +03:00
Alfredos-Panagiotis Damkalis 39befe0dfc Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-13 13:32:45 +03:00
Pierros Papadeas 980ef8abe2 Add custom sorter for frequencies on transmitters list
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-07-12 14:38:33 +03:00
Pierros Papadeas a0d398f129 Add filter control on transmitter table
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-07-12 12:14:26 +03:00
Pierros Papadeas ef1e315984 Add transmitter table view
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-07-09 20:19:23 +03:00
Alfredos-Panagiotis Damkalis 67f229d2ed Add json-ld format on API endpoints
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-01 17:14:46 +03:00
Alfredos-Panagiotis Damkalis 20153300cf Add PyLD library for JSON-LD support
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-01 16:59:20 +03:00
Alfredos-Panagiotis Damkalis 412c2aa085 Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-07-01 16:59:20 +03:00
Pierros Papadeas c98aea69fc Filter transmitters in admin by description
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-06-21 12:54:53 +03:00
deckbsd ef1c4313aa Remove python3 migration remnants
Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-05-27 20:54:39 +02:00
Pierros Papadeas d8aef58025 Add artifact model and API endpoint for new waterfall artifacts
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-05-20 19:42:31 +03:00
Alfredos-Panagiotis Damkalis 4891efd75e Remove old exported framesets from filesystem
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-05-20 18:14:23 +03:00
Alfredos-Panagiotis Damkalis 6a81937465 Create ExportedFrameset model and fix exported frames url
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-05-20 18:14:23 +03:00
Alfredos-Panagiotis Damkalis 90d860377c Update .gitignore
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-05-20 18:14:23 +03:00
Alfredos-Panagiotis Damkalis 1736abf6b2 gitlab-ci: Change from "only" to "rules"
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-05-20 17:59:25 +03:00
Pierros Papadeas 9f76d3cb25 Add filters in demod_data admin view
Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
2020-04-29 15:35:17 +03:00
Vasilis Tsiligiannis b7d2d4ff95 Fix typo in exported frames notification template
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-04-22 22:49:35 +03:00
Vasilis Tsiligiannis ddf62bf660 Fix download URL of frames exports
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-04-22 22:44:53 +03:00
Alfredos-Panagiotis Damkalis fee2c8683e Revert "Remove eslint.failAfterError()"
This reverts commit a088e5d03b.

The commit had been reverted as it was disabling failing the pipeline
in CI on errors. Without the eslint.failAfterError(), the exit status
of gulp was 0.

For solving the issue that the reverted commit was trying to solve,
continue running parallel tasks if one of them fails, gulp needs to
be run with the flag --continue, however note that this flag allows
tasks in series to continue run even if one of them fails.
2020-04-22 12:29:51 +03:00
Vasilis Tsiligiannis 79efc9a1c3 Add support for configuring CSP through environment variables
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-04-21 22:14:50 +03:00
Vasilis Tsiligiannis 2e59bc3acc Allow overriding of static and media URLs through environment variables
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-04-21 20:31:16 +03:00
Alfredos-Panagiotis Damkalis a088e5d03b Remove eslint.failAfterError()
In case of eslint error eslint.failAfterError() didn't let csslint to
finish. With removing eslint.failAfterError() both tasks run in
parallel and finish.

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-04-19 16:11:16 +03:00
Alfredos-Panagiotis Damkalis 4ba7b8cfa9 Increase character limit on mode name
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-04-10 14:50:18 +03:00
Alfredos-Panagiotis Damkalis 1f0f0767f9 Fix dashboard URL
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-04-10 11:53:59 +03:00
deckbsd 1d1c72edf4 Add link to the dashboard on satellite page
remove migration tests

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>

use urlfield instead of texfield on sat model

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>

remove the initialization of the url field

Signed-off-by: Julien Flawinne <jf.satnogs at protonmail dot com>
2020-04-09 12:22:06 +00:00
Vasilis Tsiligiannis d77e9b0f86 gitlab-ci: Use Python 3.8 image
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-04-08 12:42:05 +03:00
Vasilis Tsiligiannis e4b2161f82 gitlab-ci: Deploy API client to PyPI
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-03-29 23:08:12 +03:00
Vasilis Tsiligiannis b9b1783434 gitlab-ci: Bump dependencies versions
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-03-29 15:56:17 +03:00
Vasilis Tsiligiannis c06fe77cc1 gitlab-ci: Fix gemnasium Python scanning
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-03-29 00:55:34 +02:00
Vasilis Tsiligiannis e9334d89e1 tox: Bump environment dependencies version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-03-28 22:43:08 +02:00
Alfredos-Panagiotis Damkalis 3b2f41bded Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-03-24 14:19:23 +02:00
Vasilis Tsiligiannis 1664f68068 Fix license file to match original text version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-02-17 23:34:18 +02:00
Vasilis Tsiligiannis f656347aed gitlab-ci: Install packages for Python dependency scanning
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-02-16 13:32:12 +02:00
Vasilis Tsiligiannis b74d8ba581 gitlab-ci: Execute seperate jobs for dependency scanning
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-02-16 01:42:58 +02:00
Vasilis Tsiligiannis 7d862342f1 gitlab-ci: Enable GitLab SAST scanning
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-02-16 00:58:17 +02:00
Vasilis Tsiligiannis 80a5c561f1 gitlab-ci: Enable GitLab dependency scanning
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-02-15 21:03:26 +02:00
Vasilis Tsiligiannis 4435d64a8d gitlab-ci: Enable GitLab container scanning
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-02-15 15:04:29 +02:00
Alfredos-Panagiotis Damkalis fcf1e0852e Skip frame during decoding on binascii.Error
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-02-14 16:13:16 +02:00
Vasilis Tsiligiannis a7af3b8c69 gitlab-ci: Fix substitution when replacing 'satnogs-decoders' version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-02-14 12:45:47 +02:00
Vasilis Tsiligiannis b60e7182d1 Upload to PyPI using tox
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-02-14 00:45:11 +02:00
Vasilis Tsiligiannis 091c61dd65 Install 'dev' extra required by 'isort' tox environment
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2020-02-14 00:31:28 +02:00
Alfredos-Panagiotis Damkalis e6d0f4b417 Update Django to 2.2.10 and other python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-02-04 16:40:27 +02:00
Alfredos-Panagiotis Damkalis 36a339f6a3 Update tox version in .gitlab-ci.yml
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-02-02 01:09:20 +02:00
Alfredos-Panagiotis Damkalis 9b5da399fa Add id column in Satellite admin panel
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-02-02 00:53:18 +02:00
Alfredos-Panagiotis Damkalis 1ec0da8355 Increase character limit of name in Mode model
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-02-02 00:53:18 +02:00
Fabian P. Schmidt 80558c4c28 Update 'satellitetle' dependency
v0.8.1 -> v0.9.0

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2020-02-01 23:09:44 +01:00
Fabian P. Schmidt e681e825e2 {api|base}/tests.py: Change import order
The last dependency update changed the required import order apparently
so the 'isort' command failed.

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2020-02-01 23:06:32 +01:00
Fabian P. Schmidt f3c395a795 Update dependencies
Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2020-02-01 23:04:52 +01:00
Alfredos-Panagiotis Damkalis 3f4e5fe03f Support sentry celery and redis integrations
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-01-20 19:19:08 +02:00
Alfredos-Panagiotis Damkalis 6d86d9fb23 Update python libraries
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-01-20 19:18:30 +02:00
Alfredos-Panagiotis Damkalis d0440e4155 settings: Allow configuration of 'FILE_UPLOAD_TEMP_DIR'
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-01-14 03:57:05 +02:00
Alfredos-Panagiotis Damkalis 4b721a4488 Use API Key for accessing telemetry API endpoint
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2020-01-14 03:45:31 +02:00
Vasilis Tsiligiannis b63487e8c4 gitlab-ci: Yet another attempt to skip deployment to PyPI when triggering a tag from decoder releases
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-24 20:45:56 +02:00
Vasilis Tsiligiannis f451f255e7 docker-compose: Preserve databases data in volumes
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-20 02:58:52 +02:00
Vasilis Tsiligiannis d7ebe23830 docker-compose: Replace deprecated links with service dependencies
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-20 02:58:52 +02:00
Vasilis Tsiligiannis c764b46dff gitlab-ci: Do not deploy to PyPI when triggering a tag from decoder releases
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-20 02:24:42 +02:00
Vasilis Tsiligiannis 116033384c docker-compose: Bump 'redis' version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-20 02:24:42 +02:00
Vasilis Tsiligiannis df6c9b0304 docker-compose: Bump 'mariadb' version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-20 02:24:42 +02:00
Vasilis Tsiligiannis 632f951531 Refresh requirements
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-20 02:24:42 +02:00
Fabian P. Schmidt 97dc4e0652 Update 'satellitetle' dependency
v0.8.0 -> v0.8.1

Fixes broken TLE update due to trailing newline in AMSAT TLE source.

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2019-12-19 11:51:05 +01:00
Vasilis Tsiligiannis 4a4f96f352 Update fixtures after model changes
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-17 12:26:27 +02:00
Vasilis Tsiligiannis c231b9eaa1 gitlab-ci: Bump 'node' Docker image version
Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-17 11:25:43 +02:00
Vasilis Tsiligiannis 5c8c9fb663 Add support for setting InfluxDB client SSL verification
This patch fixes a warning of 'urllib3' when SSL is enabled.

Signed-off-by: Vasilis Tsiligiannis <acinonyx@openwrt.gr>
2019-12-16 22:16:25 +02:00
Fabian P. Schmidt 4278c6caa0 Add latest TLE to UI if possible
Fixes #264.

[v2:- Added docstrings]
Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2019-12-07 22:33:25 +01:00
deckbsd 2967e608c3 Populate decoder attribute in API
Signed-off-by: Flawinne Julien flawinne.julien@protonmail.com
2019-12-07 15:49:22 +00:00
Fabian P. Schmidt f72bd9a3f3 Update 'satellitetle' dependency
Move 'satellitetle' from 0.7.0 to 0.8.0,
fixes update_satellite management command and update_all_tle task for
satellites which are not part of any list in celestrak but available
via the Celestrak satcat/tle.php endpoint (currently only NARSSCube-1).

Both commands use `fetch_tle_from_celestrak` (directly and indirectly),
which was broken due to an API change by Celestrak.

Signed-off-by: Fabian P. Schmidt <kerel@mailbox.org>
2019-12-01 19:41:49 +01:00
deckbsd 062152aeec add uplink_mode field for transciever and transponder transmitter type
Signed-off-by: Flawinne Julien flawinne.julien at protonmail dot com

add uplink_mode field for transciever and transponder
Signed-off-by: Flawinne Julien flawinne.julien@protonmail.com

adapt unit test for transmitter model

Update migration for taking account of the invert value

update uplink_mode migration condition

Not request TLE with NORAD ID above 99000

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>

rename mode transmitter field to downlink_mode

adapt unit test for transmitter model

update uplink_mode migration condition

add uplink_mode field for transciever and transponder transmitter type
Signed-off-by: Flawinne Julien flawinne.julien at protonmail dot com

keep mode id

add forgotten field mode id

add missing ,

fix yapf error
2019-12-01 09:43:44 +00:00
235 changed files with 39309 additions and 17653 deletions

2
.gitignore vendored
View File

@ -6,6 +6,7 @@ env
.env
.cache
*.egg-info
*.DS_Store
# Logs
*.log
@ -27,6 +28,7 @@ dist
# Media & Static
media
/staticfiles/*
/db/static/lib/
node_modules

View File

@ -1,12 +1,12 @@
variables:
GITLAB_CI_IMAGE_ALPINE: 'alpine:3.9'
GITLAB_CI_IMAGE_DOCKER: 'docker:18.09'
GITLAB_CI_IMAGE_NODE: 'node:11.13'
GITLAB_CI_IMAGE_PYTHON: 'python:3'
GITLAB_CI_IMAGE_OPENAPI_GENERATOR_CLI: 'openapitools/openapi-generator-cli'
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.8.0'
GITLAB_CI_PYPI_TWINE: 'twine~=1.13.0'
GITLAB_CI_PYPI_TOX: 'tox~=3.20.0'
stages:
- schema
- api
@ -14,20 +14,34 @@ stages:
- 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 generateschema > satnogs-db-api-client/api-schema.yml
- >-
./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:
- >-
@ -37,13 +51,23 @@ api:
-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
needs: []
image: ${GITLAB_CI_IMAGE_NODE}
script:
- npm ci
@ -55,13 +79,17 @@ static_js_css:
- db/static/lib
static:
stage: static
needs: []
image: ${GITLAB_CI_IMAGE_PYTHON}
before_script:
- pip install "$GITLAB_CI_PYPI_TOX"
script:
- 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"
@ -75,28 +103,51 @@ docs:
- docs/_build/html
build:
stage: build
needs:
- job: static_js_css
artifacts: true
image: ${GITLAB_CI_IMAGE_PYTHON}
before_script:
- pip install "$GITLAB_CI_PYPI_TOX"
script:
- rm -rf dist
- tox -e build
- cd satnogs-db-api-client
- rm -rf dist
- python setup.py sdist bdist_wheel
artifacts:
expire_in: 1 week
when: always
paths:
- dist
build_api:
stage: build
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
- tox -e build
artifacts:
expire_in: 1 week
when: always
paths:
- satnogs-db-api-client/dist
# 'test' stage
test:
stage: test
needs:
- job: static_js_css
artifacts: true
image: ${GITLAB_CI_IMAGE_PYTHON}
before_script:
- pip install "$GITLAB_CI_PYPI_TOX"
script:
- tox -e deps,pytest
# 'deploy' stage
docker:
stage: deploy
image: ${GITLAB_CI_IMAGE_DOCKER}
@ -106,8 +157,6 @@ docker:
- apk --update add py-pip
- pip install "$GITLAB_CI_PYPI_DOCKER_COMPOSE"
script:
- |
[ -z "$SATNOGS_DECODERS_VERSION" ] || sed -i 's/^\(satnogsdecoders\).*/\1=='"$SATNOGS_DECODERS_VERSION"'/' requirements.txt
- |
[ -z "$CI_REGISTRY_IMAGE" ] || {
CACHE_IMAGE="$CI_REGISTRY_IMAGE/satnogs-db:$CI_COMMIT_REF_NAME"
@ -143,23 +192,72 @@ deploy:
stage: deploy
image: ${GITLAB_CI_IMAGE_PYTHON}
before_script:
- pip install "$GITLAB_CI_PYPI_TWINE"
- pip install "$GITLAB_CI_PYPI_TOX"
script:
- rm -rf dist
- python setup.py sdist bdist_wheel
- twine upload -u "$PYPI_USERNAME" -p "$PYPI_PASSWORD" 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
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:
@ -168,23 +266,66 @@ trigger_master:
- $PIPELINE_TRIGGERS_MASTER
trigger_latest:
stage: trigger
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
pages:
stage: deploy
image: ${GITLAB_CI_IMAGE_ALPINE}
script:
- mv docs/_build/html/ public/
artifacts:
paths:
- public
only:
- tags
# '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

View File

@ -7,3 +7,11 @@ ignored-argument-names=args|kwargs
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

12
.readthedocs.yml 100644
View File

@ -0,0 +1,12 @@
---
version: '2'
formats:
- 'epub'
- 'pdf'
python:
version: '3.8'
install:
- path: '.'
- requirements: 'docs/requirements.txt'
build:
image: 'latest'

View File

@ -1,5 +1,5 @@
FROM python:3
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 \

View File

@ -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/>.

26
README-upstream.md 100644
View File

@ -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
View File

@ -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/projects/satnogs-db/en/stable/) 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/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)
# 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--2019-Libre%20Space%20Foundation-6672D8.svg)](https://librespacefoundation.org/)

View File

View File

@ -1,7 +0,0 @@
# -*- coding: utf-8 -*-
"""SatNOGS DB Auth0 login module admin class"""
from __future__ import unicode_literals
# from django.contrib import admin
# Register your models here.

View File

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
"""SatNOGS DB Auth0 login app config"""
from __future__ import unicode_literals
from django.apps import AppConfig
class Auth0LoginConfig(AppConfig):
"""Set the name of the django app for auth0login"""
name = 'auth0login'

View File

@ -1,40 +0,0 @@
"""SatNOGS DB Auth0 login module auth backend"""
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 auth_html(self):
"""Return the login endpoint."""
return "https://" + self.setting('DOMAIN') + "/login/auth0"
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']
}

View File

@ -1,7 +0,0 @@
# -*- coding: utf-8 -*-
"""SatNOGS DB Auth0 login module models"""
from __future__ import unicode_literals
# from django.db import models
# Create your models here.

View File

@ -1,7 +0,0 @@
# -*- coding: utf-8 -*-
"""SatNOGS DB Auth0 login module test suites"""
from __future__ import unicode_literals
# from django.test import TestCase
# Create your tests here.

View File

@ -1,10 +0,0 @@
"""SatNOGS DB Auth0 login module URL routers"""
from django.conf.urls import include, url
from . import views
urlpatterns = [
url('^$', views.index),
url(r'^', include(('django.contrib.auth.urls', 'auth'), namespace='auth')),
url(r'^', include(('social_django.urls', 'social'), namespace='social')),
]

View File

@ -1,9 +0,0 @@
"""SatNOGS DB Auth0 login module views"""
from __future__ import unicode_literals
from django.shortcuts import render
def index(request):
"""Returns the index view"""
return render(request, 'index.html')

View File

@ -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

10
conftest.py 100644
View File

@ -0,0 +1,10 @@
"""pytest configuration file"""
import pytest
@pytest.fixture(scope='session')
def celery_config():
return {
'broker_url': 'memory://',
'result_backend': 'rpc',
}

View File

@ -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,13 +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 .

View File

@ -1,6 +1,4 @@
"""The core django app for SatNOGS DB"""
from __future__ import absolute_import
from ._version import get_versions
from .celery import APP as celery_app # noqa

View File

@ -6,18 +6,17 @@
# 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."""
from __future__ import absolute_import, division, print_function, \
unicode_literals
import errno
import os
import re
import subprocess
import sys
from typing import Callable, Dict
import functools
def get_keywords():
@ -55,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:
@ -74,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
@ -96,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):
@ -116,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" %
@ -141,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
@ -164,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
@ -180,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
@ -193,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:
@ -202,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,
@ -217,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*
@ -228,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()
@ -255,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
@ -271,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
@ -296,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
@ -340,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
@ -383,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"]:
@ -459,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":
@ -498,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,

View File

@ -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
]

View File

@ -1,17 +1,31 @@
"""SatNOGS DB django rest framework Filters class"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
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 DemodData, Satellite, Transmitter
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
@ -23,9 +37,49 @@ class TransmitterViewFilter(FilterSet):
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 = ['uuid', 'mode', 'type', 'satellite__norad_cat_id', 'alive', 'status', 'service']
fields = [
'uuid', 'mode', 'uplink_mode', 'type', 'satellite__norad_cat_id', 'alive', 'status',
'service'
]
class SatelliteViewFilter(FilterSet):
@ -33,21 +87,82 @@ class SatelliteViewFilter(FilterSet):
filter on decayed field
"""
in_orbit = filters.BooleanFilter(field_name='decayed', label='In orbit', lookup_expr='isnull')
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):
"""SatNOGS DB Telemetry API View Filter"""
satellite = django_filters.NumberFilter(
field_name='satellite__norad_cat_id', lookup_expr='exact'
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', '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',
]

View File

@ -1,9 +1,6 @@
"""
Custom pagination classes for REST framework
"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
@ -20,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)

18
db/api/parsers.py 100644
View File

@ -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

23
db/api/perms.py 100644
View File

@ -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)

View File

@ -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()

View File

@ -1,115 +1,561 @@
"""SatNOGS DB API serializers, django rest framework"""
# pylint: disable=R0201
from __future__ import absolute_import, division, print_function, \
unicode_literals
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 TRANSMITTER_STATUS, DemodData, Mode, Satellite, \
Transmitter
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()
mode_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='created')
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', 'mode', 'invert', 'baud',
'norad_cat_id', 'status', 'updated', 'citation', 'service'
'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 mode ID"""
"""Returns downlink mode id"""
try:
return obj.mode.id
except Exception: # pylint: disable=W0703
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 mode name"""
"""Returns downlink mode name"""
try:
return obj.mode.name
except Exception: # pylint: disable=W0703
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):
"""Returns Satellite NORAD ID"""
return obj.satellite.norad_cat_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', 'app_source', 'schema', 'decoded', 'frame', 'observer',
'timestamp'
'sat_id', 'norad_cat_id', 'transmitter', 'app_source', 'decoded', 'frame', 'observer',
'timestamp', 'version', 'observation_id', 'station_id', 'associated_satellites'
)
def get_norad_cat_id(self, obj):
"""Returns Satellite NORAD ID for this Transmitter"""
return obj.satellite.norad_cat_id
@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):
"""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: # pylint: disable=W0703
return ''
def get_schema(self, obj):
"""Returns Transmitter telemetry schema"""
try:
return obj.payload_telemetry.schema
except Exception: # pylint: disable=W0703
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', 'app_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', )

View File

@ -1,13 +1,10 @@
"""SatNOGS DB API test suites"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
import pytest
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 DemodDataFactory, ModeFactory, SatelliteFactory, \
TransmitterFactory
from db.base.tests import DemodDataFactory, ModeFactory, SatelliteFactory, TransmitterFactory
@pytest.mark.django_db(transaction=True)
@ -48,12 +45,36 @@ class SatelliteViewApiTest(TestCase):
response = self.client.get('/api/satellites/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_retrieve(self):
"""Test the Satellite API retrieval"""
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.norad_cat_id), format='json'
'/api/satellites/{0}/'.format(self.satellite.satellite_entry.norad_cat_id),
format='json'
)
self.assertContains(response, self.satellite.name)
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)
@ -64,7 +85,8 @@ 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()
@ -73,6 +95,16 @@ class TransmitterViewApiTest(TestCase):
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):
"""Test the Transmitter API retrieval"""
response = self.client.get(
@ -82,22 +114,145 @@ class TransmitterViewApiTest(TestCase):
@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):
def test_list_anonymous(self):
"""Test the Telemetry API listing"""
response = self.client.get('/api/telemetry/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
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)

View File

@ -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

View File

@ -1,16 +1,24 @@
"""SatNOGS DB django rest framework API url routings"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
from django.urls import include, path
from rest_framework import routers
from db.api import views
ROUTER = routers.DefaultRouter()
ROUTER.register(r'modes', views.ModeView)
ROUTER.register(r'satellites', views.SatelliteView)
ROUTER.register(r'transmitters', views.TransmitterView)
ROUTER.register(r'telemetry', views.TelemetryView)
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)
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))
]

View File

@ -1,78 +1,627 @@
"""SatNOGS DB API django rest framework Views"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
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
from rest_framework.permissions import AllowAny
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 filters, pagination, serializers
from db.base.models import DemodData, Mode, Satellite, Transmitter
from db.base.tasks import update_satellite
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): # pylint: disable=R0901
"""SatNOGS DB Mode API view class"""
@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): # pylint: disable=R0901
"""SatNOGS DB Satellite API view class"""
queryset = Satellite.objects.all()
serializer_class = serializers.SatelliteSerializer
filter_class = filters.SatelliteViewFilter
lookup_field = 'norad_cat_id'
class TransmitterView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
"""SatNOGS DB Transmitter API view class"""
queryset = Transmitter.objects.all()
serializer_class = serializers.TransmitterSerializer
filter_class = filters.TransmitterViewFilter
lookup_field = 'uuid'
class TelemetryView( # pylint: disable=R0901
@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):
"""SatNOGS DB Telemetry API view class"""
queryset = DemodData.objects.all()
"""
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
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)
@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
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'
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'):
@ -83,9 +632,89 @@ class TelemetryView( # pylint: disable=R0901
# 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

View File

@ -1,19 +1,17 @@
"""Defines functions and settings for the django admin interface"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
from datetime import datetime
from socket import error as socket_error
from django.conf.urls import url
from django.contrib import admin, messages
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.urls import reverse
from django.urls import re_path, reverse
from django.utils.timezone import now
from db.base.models import DemodData, Mode, Satellite, Telemetry, \
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
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)
@ -22,12 +20,170 @@ class ModeAdmin(admin.ModelAdmin):
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):
"""Defines Satellite view in django admin UI"""
list_display = ('name', 'norad_cat_id', 'status', 'decayed')
search_fields = ('name', 'norad_cat_id')
list_filter = ('status', 'decayed')
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):
"""Returns django urls for the Satellite view
@ -37,17 +193,57 @@ class SatelliteAdmin(admin.ModelAdmin):
:returns: Django urls for the Satellite admin view
"""
urls = super(SatelliteAdmin, self).get_urls()
urls = super().get_urls()
my_urls = [
url(r'^check_celery/$', self.check_celery, name='check_celery'),
url(
r'^decode_all_data/(?P<norad>[0-9]+)/$',
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 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
@ -72,63 +268,110 @@ class SatelliteAdmin(admin.ModelAdmin):
return HttpResponseRedirect(reverse('admin:index'))
def decode_all_data(self, request, norad): # pylint: disable=R0201
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 norad ID. This could be very resource
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 norad: the NORAD ID for the satellite to decode
:param sat_id: the Satellite Identifier for the satellite to decode
:returns: Admin home page
"""
decode_all_data.delay(norad)
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 = (
'uuid', 'description', 'satellite', 'service', 'type', 'mode', 'baud', 'downlink_low',
'downlink_high', 'downlink_drift', 'uplink_low', 'uplink_high', 'uplink_drift', 'reviewed',
'approved', 'status', 'created', 'citation', 'user'
'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'
)
search_fields = ('satellite__id', 'uuid', 'satellite__name', 'satellite__norad_cat_id')
list_filter = (
'reviewed',
'approved',
'type',
'status',
'service',
'mode',
'downlink_mode',
'uplink_mode',
'baud',
)
readonly_fields = ('uuid', 'satellite')
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', 'mode', 'baud', 'downlink_low',
'downlink_high', 'downlink_drift', 'uplink_low', 'uplink_high', 'uplink_drift', 'status',
'created', 'citation', 'user'
'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'
)
search_fields = ('satellite__id', 'uuid', 'satellite__name', 'satellite__norad_cat_id')
list_filter = (
'type',
'mode',
'downlink_mode',
'uplink_mode',
'baud',
'service',
)
readonly_fields = (
'uuid', 'description', 'status', 'type', 'uplink_low', 'uplink_high', 'uplink_drift',
'downlink_low', 'downlink_high', 'downlink_drift', 'mode', 'invert', 'baud', 'satellite',
'reviewed', 'approved', 'created', 'citation', 'user'
)
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
@ -136,7 +379,7 @@ class TransmitterSuggestionAdmin(admin.ModelAdmin):
:returns: list of actions the user can take on TransmitterSuggestion
"""
actions = super(TransmitterSuggestionAdmin, self).get_actions(request)
actions = super().get_actions(request)
if not request.user.has_perm('base.delete_transmittersuggestion'):
if 'delete_selected' in actions:
del actions['delete_selected']
@ -151,13 +394,9 @@ class TransmitterSuggestionAdmin(admin.ModelAdmin):
queryset_size = len(queryset)
for entry in queryset:
entry.approved = True
entry.reviewed = True
entry.created = datetime.utcnow()
entry.user = request.user
entry.reviewed = now()
entry.reviewer = request.user
entry.save()
# After creating the new approved entries, we update the suggestion entries as reviewed
# Note that queryset.update doesn't use model's save() that creates new entries
queryset.update(reviewed=True, approved=True)
if queryset_size == 1:
self.message_user(request, "Transmitter suggestion was successfully approved")
else:
@ -173,14 +412,10 @@ class TransmitterSuggestionAdmin(admin.ModelAdmin):
"""
queryset_size = len(queryset)
for entry in queryset:
entry.created = datetime.utcnow()
entry.user = request.user
entry.approved = False
entry.reviewed = True
entry.reviewed = now()
entry.reviewer = request.user
entry.save()
# After creating the new approved entries, we update the suggestion entries as reviewed
# Note that queryset.update doesn't use model's save() that creates new entries
queryset.update(reviewed=True, approved=False)
if queryset_size == 1:
self.message_user(request, "Transmitter suggestion was successfully rejected")
else:
@ -193,32 +428,119 @@ class TransmitterSuggestionAdmin(admin.ModelAdmin):
class TransmitterAdmin(admin.ModelAdmin):
"""Defines Transmitter view in django admin UI"""
list_display = (
'uuid', 'description', 'satellite', 'service', 'type', 'mode', 'baud', 'downlink_low',
'downlink_high', 'downlink_drift', 'uplink_low', 'uplink_high', 'uplink_drift', 'status',
'created', 'citation', 'user'
'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'
)
search_fields = ('satellite__id', 'uuid', 'satellite__name', 'satellite__norad_cat_id')
list_filter = (
'type',
'status',
'service',
'mode',
'downlink_mode',
'uplink_mode',
'baud',
)
readonly_fields = ('uuid', 'satellite')
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(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 satellite_name(self, obj): # pylint: disable=no-self-use
"""Return the satellite name"""
return obj.satellite.satellite_entry.name
def get_urls(self):
"""Returns django urls for Tle view
update_tle_sets -- url for the update_tle_sets function
: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
def update_tle_sets(self, request): # pylint: disable=R0201
"""Returns the admin home page, while triggering a Celery update tle sets task
:returns: Admin home page
"""
update_tle_sets.delay()
messages.success(request, 'Update TLE sets task was triggered successfully!')
return redirect(reverse('admin:index'))
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):
"""Defines Telemetry view in django admin UI"""
list_display = ('name', 'decoder')
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):
"""Defines DemodData view in django admin UI"""
list_display = ('id', 'satellite', 'app_source', 'observer')
search_fields = ('transmitter__uuid', 'satellite__norad_cat_id', 'observer')
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): # pylint: disable=R0201
"""Returns the Satellite object associated with this DemodData
@ -227,3 +549,17 @@ class DemodDataAdmin(admin.ModelAdmin):
: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')

11
db/base/apps.py 100644
View 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

View File

@ -1,7 +1,4 @@
"""SatNOGS DB django context processors"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
from django.conf import settings
from django.template.loader import render_to_string
from satnogsdecoders import __version__ as satnogsdecoders_version
@ -45,6 +42,15 @@ def logout_block(request):
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__)}

View File

@ -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 Radios 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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,152 @@
"""SatNOGS DB django base Forms class"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
from django import forms
from bootstrap_modal_forms.forms import BSModalForm, BSModalModelForm
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.forms import ModelChoiceField, TextInput
from django.utils.translation import gettext_lazy as _
from db.base.models import Transmitter, TransmitterEntry
from db.base.models import Satellite, SatelliteEntry, Transmitter, TransmitterEntry
def existing_uuid(value):
"""ensures the UUID is existing and valid"""
try:
Transmitter.objects.get(uuid=value)
except Transmitter.DoesNotExist:
except Transmitter.DoesNotExist as error:
raise ValidationError(
_('%(value)s is not a valid uuid'),
code='invalid',
params={'value': value},
)
) from error
class TransmitterEntryForm(forms.ModelForm):
class TransmitterCreateForm(BSModalModelForm): # pylint: disable=too-many-ancestors
"""Model Form class for TransmitterEntry objects"""
uuid = forms.CharField(required=False, validators=[existing_uuid])
class Meta:
model = TransmitterEntry
fields = [
'description', 'status', 'type', 'uplink_low', 'uplink_high', 'downlink_low',
'downlink_high', 'uplink_drift', 'downlink_drift', 'mode', 'invert', 'baud',
'satellite', 'citation', 'service'
'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']

View File

@ -1,7 +1,4 @@
"""Helper functions for SatNOGS DB"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.authtoken.models import Token
@ -17,10 +14,13 @@ def gridsquare(lat, lng):
:returns: a string of the grid square, ie: EM69uf
"""
if not -180 <= lng < 180:
return False
if not -90 <= lat < 90:
return False
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
@ -38,15 +38,15 @@ def gridsquare(lat, lng):
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
grid_lon_sq + grid_lat_sq + grid_lon_field + grid_lat_field + grid_lon_subsq
+ grid_lat_subsq
)
return qth
def get_apikey(user):
"""If necessary, create, then return an API key for a 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

View File

@ -1,26 +0,0 @@
"""SatNOGS DB django management command to delete satellites"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
from django.core.exceptions import ObjectDoesNotExist
from django.core.management.base import BaseCommand
from db.base.models import Satellite
class Command(BaseCommand):
"""django management command to delete satellites"""
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 ObjectDoesNotExist:
self.stderr.write('Satellite with Identifier {} does not exist'.format(norad_id))

View File

@ -1,24 +0,0 @@
"""SatNOGS DB django management command to fetch satellites"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
from django.core.management.base import BaseCommand
from db.base.tasks import update_satellite
class Command(BaseCommand):
"""django management command to fetch satellites"""
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

View File

@ -1,7 +1,4 @@
"""SatNOGS DB django management command to initialize a new database"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
from django.core.management import call_command
from django.core.management.base import BaseCommand
@ -18,6 +15,9 @@ class Command(BaseCommand):
# 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')

View File

@ -1,10 +1,7 @@
"""SatNOGS DB django management command to update TLE entries"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
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):
@ -12,4 +9,4 @@ class Command(BaseCommand):
help = 'Update TLEs for existing Satellites'
def handle(self, *args, **options):
update_all_tle()
update_tle_sets()

View File

@ -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='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, on_delete=django.db.models.deletion.CASCADE, 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', on_delete=django.db.models.deletion.SET_NULL, 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),
),
]

View File

@ -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

View File

@ -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='Ideally: 250x250', upload_to='satellites', blank=True),
),
]

View File

@ -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

View File

@ -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),
),
]

View File

@ -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

View File

@ -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=[('manual', 'manual'), ('network', 'network'), ('sids', 'sids')], default='sids', max_length=7),
),
migrations.AddField(
model_name='demoddata',
name='station',
field=models.CharField(default='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'),
),
]

View File

@ -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

View File

@ -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),
),
]

View File

@ -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

View File

@ -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),
),
]

View File

@ -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

View File

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2019-03-29 19:11
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
def add_suggestion_permissions(apps, schema_editor):

View File

@ -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),
),
]

View File

@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2019-04-04 04:36
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django.db.models import Max
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):

View File

@ -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),
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-20 23:06
from __future__ import unicode_literals
from django.db import migrations

View File

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-21 01:43
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-02 05:59
from __future__ import unicode_literals
from django.db import migrations, models

View File

@ -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',
),
]

View File

@ -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),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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)),
],
),
]

View File

@ -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)),
],
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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),
),
]

View File

@ -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'),
),
]

View File

@ -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')]},
),
]

View File

@ -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'])]),
),
]

View File

@ -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',
),
]

View File

@ -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),
),
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.6 on 2017-05-08 15:49
from __future__ import unicode_literals
# Generated by Django 2.2.14 on 2020-09-21 16:17
from django.db import migrations, models
@ -8,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0006_auto_20170323_1715'),
('base', '0026_add_satellite_norad_follow_id_field'),
]
operations = [
migrations.AddField(
migrations.AlterField(
model_name='satellite',
name='status',
field=models.CharField(choices=[('alive', 'alive'), ('dead', 'dead'), ('re-entered', 're-entered')], default='alive', max_length=10),
field=models.CharField(choices=[('alive', 'alive'), ('dead', 'dead'), ('future', 'future'), ('re-entered', 're-entered')], default='alive', max_length=10),
),
]

View File

@ -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',
),
]

View File

@ -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',
),
]

View File

@ -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),
),
]

View File

@ -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'),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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',
),
]

View File

@ -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',
),
]

View File

@ -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),
]

View File

@ -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',
),
]

View File

@ -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'},
),
]

View File

@ -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),
]

View File

@ -0,0 +1,66 @@
# Generated by Django 3.1.8 on 2021-04-15 06:46
from django.db import migrations, models
import django.db.models.deletion
def initialize_new_satellite_fields(apps, schema_editor):
Satellite = apps.get_model('base', 'Satellite')
DemodData = apps.get_model('base', 'DemodData')
ExportedFrameset = apps.get_model('base', 'ExportedFrameset')
LatestTleSet = apps.get_model('base', 'LatestTleSet')
Telemetry = apps.get_model('base', 'Telemetry')
Tle = apps.get_model('base', 'Tle')
TransmitterEntry = apps.get_model('base', 'TransmitterEntry')
for new_satellite in Satellite.objects.all():
DemodData.objects.filter(satellite=new_satellite.satellite_entry).update(new_satellite=new_satellite)
ExportedFrameset.objects.filter(satellite=new_satellite.satellite_entry).update(new_satellite=new_satellite)
LatestTleSet.objects.filter(satellite=new_satellite.satellite_entry).update(new_satellite=new_satellite)
Telemetry.objects.filter(satellite=new_satellite.satellite_entry).update(new_satellite=new_satellite)
Tle.objects.filter(satellite=new_satellite.satellite_entry).update(new_satellite=new_satellite)
TransmitterEntry.objects.filter(satellite=new_satellite.satellite_entry).update(new_satellite=new_satellite)
def reverse_initialize_new_satellite_fields(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('base', '0040_update_and_create_satellite_models'),
]
operations = [
migrations.AddField(
model_name='demoddata',
name='new_satellite',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='base.satellite'),
),
migrations.AddField(
model_name='exportedframeset',
name='new_satellite',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='base.satellite'),
),
migrations.AddField(
model_name='latesttleset',
name='new_satellite',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='base.satellite'),
),
migrations.AddField(
model_name='telemetry',
name='new_satellite',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='base.satellite'),
),
migrations.AddField(
model_name='tle',
name='new_satellite',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='base.satellite'),
),
migrations.AddField(
model_name='transmitterentry',
name='new_satellite',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='base.satellite'),
),
migrations.RunPython(initialize_new_satellite_fields, reverse_initialize_new_satellite_fields),
]

View File

@ -0,0 +1,67 @@
# Generated by Django 3.1.8 on 2021-04-20 03:40
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('base', '0041_create_new_satellite_foreign_keys_for_models'),
]
operations = [
migrations.AlterModelOptions(
name='telemetry',
options={'ordering': ['new_satellite__satellite_entry__norad_cat_id'], 'verbose_name_plural': 'Telemetries'},
),
migrations.RemoveField(
model_name='demoddata',
name='satellite',
),
migrations.RemoveField(
model_name='exportedframeset',
name='satellite',
),
migrations.RemoveField(
model_name='latesttleset',
name='satellite',
),
migrations.RemoveField(
model_name='telemetry',
name='satellite',
),
migrations.RemoveField(
model_name='tle',
name='satellite',
),
migrations.RemoveField(
model_name='transmitterentry',
name='satellite',
),
migrations.AlterField(
model_name='demoddata',
name='new_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='latesttleset',
name='new_satellite',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='latest_tle_set', to='base.satellite'),
),
migrations.AlterField(
model_name='telemetry',
name='new_satellite',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='telemetries', to='base.satellite'),
),
migrations.AlterField(
model_name='tle',
name='new_satellite',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tle_sets', to='base.satellite'),
),
migrations.AlterField(
model_name='transmitterentry',
name='new_satellite',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transmitter_entries', to='base.satellite'),
),
]

View File

@ -0,0 +1,47 @@
# Generated by Django 3.1.8 on 2021-04-20 03:44
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('base', '0042_remove_satellite_foreign_keys_for_models'),
]
operations = [
migrations.AlterModelOptions(
name='telemetry',
options={'ordering': ['satellite__satellite_entry__norad_cat_id'], 'verbose_name_plural': 'Telemetries'},
),
migrations.RenameField(
model_name='demoddata',
old_name='new_satellite',
new_name='satellite',
),
migrations.RenameField(
model_name='exportedframeset',
old_name='new_satellite',
new_name='satellite',
),
migrations.RenameField(
model_name='latesttleset',
old_name='new_satellite',
new_name='satellite',
),
migrations.RenameField(
model_name='telemetry',
old_name='new_satellite',
new_name='satellite',
),
migrations.RenameField(
model_name='tle',
old_name='new_satellite',
new_name='satellite',
),
migrations.RenameField(
model_name='transmitterentry',
old_name='new_satellite',
new_name='satellite',
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.1.5 on 2021-04-21 04:21
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('base', '0043_rename_new_satellite_to_satellite_for_models'),
]
operations = [
migrations.AlterField(
model_name='latesttleset',
name='satellite',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='latest_tle_set', to='base.satellite'),
),
migrations.AlterField(
model_name='satellite',
name='satellite_identifier',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='satellite', to='base.satelliteidentifier'),
),
]

Some files were not shown because too many files have changed in this diff Show More