diff --git a/.env-template b/.env-template index 7982d58..9233ed0 100644 --- a/.env-template +++ b/.env-template @@ -2,4 +2,5 @@ DEBUG=on DATABASE_URL=sqlite:///db.sqlite3 ADMIN_PATH=admin/ TBA_API_KEY= -SERVER_IP=https://127.0.0.1 \ No newline at end of file +SERVER_IP=https://127.0.0.1 +SERVER_MESSAGE= \ No newline at end of file diff --git a/README.md b/README.md index 233b412..c437ebb 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,8 @@ Set the following global environment variables: - `DEBUG` -> `False` - `DATABASE_URL` -> `${db.DATABASE_URL}` (This works on DigitalOcean, this may not work on every hosting provider) +Additionally, if you wish to show a custom message to the user on each page (demonstration server, currently undergoing maintenance), set the `SERVER_MESSAGE` environment variable to the message you wish to display. + ## Development ### djlint This project uses `djlint` to lint the templates. You can run this using the following command diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index bb6deb8..5ae3e87 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -4,6 +4,19 @@ This document describes the roadmap for Open Scouting, and roughly when each fea --- ## To-Do + +### `v0.0.6-alpha` +- [ ] [#16](https://github.com/nfoert/open-scouting/issues/16) - Show data in the table differently depending on the kind of field +- [ ] [#23](https://github.com/nfoert/open-scouting/issues/23) - URL Parameter support on `/index` +- [ ] [#20](https://github.com/nfoert/open-scouting/issues/20) - Allow the user to create an account +- [ ] [#13](https://github.com/nfoert/open-scouting/issues/13) - Save the username and team number to Data + +--- +## Completed +### `v0.0.5-alpha` +- [x] [#41](https://github.com/nfoert/open-scouting/issues/41) - Implement a way to show a server message +- [x] Implement 2025 season fields and examples + ### `v0.0.4-alpha` - [x] [#9](https://github.com/nfoert/open-scouting/issues/9) - Create a wiki page for how season_fields.py should be formatted - [x] [#7](https://github.com/nfoert/open-scouting/issues/7) - Add a field for additional notes @@ -13,14 +26,6 @@ This document describes the roadmap for Open Scouting, and roughly when each fea - [x] [#38](https://github.com/nfoert/open-scouting/issues/38) - Add demo mode - [x] [#39](https://github.com/nfoert/open-scouting/issues/39) - Non-ASCII characters in event name breaks data submission -### `v0.0.5-alpha` -- [ ] [#16](https://github.com/nfoert/open-scouting/issues/16) - Show data in the table differently depending on the kind of field -- [ ] [#23](https://github.com/nfoert/open-scouting/issues/23) - URL Parameter support on `/index` -- [ ] [#20](https://github.com/nfoert/open-scouting/issues/20) - Allow the user to create an account -- [ ] [#13](https://github.com/nfoert/open-scouting/issues/13) - Save the username and team number to Data - ---- -## Completed ### `v0.0.3-alpha` - [x] [#24](https://github.com/nfoert/open-scouting/issues/24) - Implement a collapsible menu - [x] [#14](https://github.com/nfoert/open-scouting/issues/14) - Add a dark mode toggle diff --git a/docs/Setting_Up_a_New_Season.md b/docs/Setting_Up_a_New_Season.md new file mode 100644 index 0000000..915de4c --- /dev/null +++ b/docs/Setting_Up_a_New_Season.md @@ -0,0 +1,20 @@ +# Setting Up a New Season + +This page documents which steps should be followed at the start of a season to quickly prepare Open Scouting for a new game + +These steps should be followed as early in the season as possible, preferably on kickoff day if possible + +## 1. Update `season_fields.py` +- Add a new year to the `season_fields.py` file. Follow [Formatting Season Fields](./Formatting_Season_Fields.md) for how to do this. Data entries should be kept simple and not complicated, simplify or remove any fields that you can, and the very minimum amount of fields should be kept required, the goal is to make the process as easy and quick as possible for scouts. + +## 2. Update `views.py` and `models.py` +- Navigate to `/main/views.py` + - Add the new year to the `YEARS` array + - Add to the `get_season_data_from_year` and `get_demo_data_from_year` functions to return the new year's data +- Navigate to `/main/models.py` + - Add the new year to the `YEARS` array as a tuple, just match with the years that are already in this array + +## 3. Create demo data +- Follow the instructions in [Creating Demonstration Data](./Creating_Demonstration_Data.md) for how to do this + +Open Scouting should now be updated to the new season! You should navigate around and ensure that everything is working as expected \ No newline at end of file diff --git a/scouting/main/demo_data.py b/scouting/main/demo_data.py index 93e3cad..fc812be 100644 --- a/scouting/main/demo_data.py +++ b/scouting/main/demo_data.py @@ -87,3 +87,87 @@ {"name": "amp_shot", "type": "integer", "value": 1}, ], ] + +reefscape = [ + [ + {"name": "team_number", "type": "large_integer", "value": "1"}, + {"name": "match_number", "type": "large_integer", "value": "001"}, + {"name": "auton_moved", "type": "boolean", "value": True}, + {"name": "feeder_pickup", "type": "boolean", "value": True}, + {"name": "park_in_barge_zone", "type": "boolean", "value": False}, + {"name": "climb_shallow", "type": "boolean", "value": True}, + {"name": "climb_deep", "type": "boolean", "value": False}, + {"name": "coral_levels", "type": "multiple_choice", "value": "Level 1"}, + {"name": "auton_coral_scored", "type": "integer", "value": 2}, + {"name": "coral_scored", "type": "integer", "value": 4}, + {"name": "algae_scored_in_processor", "type": "integer", "value": 2}, + ], + [ + {"name": "team_number", "type": "large_integer", "value": "2"}, + {"name": "match_number", "type": "large_integer", "value": "1"}, + {"name": "auton_moved", "type": "boolean", "value": True}, + {"name": "feeder_pickup", "type": "boolean", "value": False}, + {"name": "park_in_barge_zone", "type": "boolean", "value": True}, + {"name": "climb_shallow", "type": "boolean", "value": False}, + {"name": "climb_deep", "type": "boolean", "value": False}, + { + "name": "notes", + "type": "text", + "value": "Could score in the reef but only on level 1", + }, + {"name": "coral_levels", "type": "multiple_choice", "value": "Level 1"}, + {"name": "auton_coral_scored", "type": "integer", "value": 1}, + {"name": "auton_algae_scored_in_processor", "type": "integer", "value": 1}, + {"name": "algae_scored_in_net", "type": "integer", "value": 2}, + {"name": "algae_scored_in_processor", "type": "integer", "value": 3}, + ], + [ + {"name": "team_number", "type": "large_integer", "value": "3"}, + {"name": "match_number", "type": "large_integer", "value": "1"}, + {"name": "auton_moved", "type": "boolean", "value": True}, + {"name": "feeder_pickup", "type": "boolean", "value": True}, + {"name": "park_in_barge_zone", "type": "boolean", "value": False}, + {"name": "climb_shallow", "type": "boolean", "value": True}, + {"name": "climb_deep", "type": "boolean", "value": False}, + {"name": "notes", "type": "text", "value": "Good coral scoring bot"}, + {"name": "coral_levels", "type": "multiple_choice", "value": "Level 1"}, + {"name": "auton_coral_scored", "type": "integer", "value": 4}, + {"name": "coral_scored", "type": "integer", "value": 6}, + ], + [ + {"name": "team_number", "type": "large_integer", "value": "4"}, + {"name": "match_number", "type": "large_integer", "value": "1"}, + {"name": "auton_moved", "type": "boolean", "value": False}, + {"name": "feeder_pickup", "type": "boolean", "value": False}, + {"name": "park_in_barge_zone", "type": "boolean", "value": True}, + {"name": "climb_shallow", "type": "boolean", "value": False}, + {"name": "climb_deep", "type": "boolean", "value": False}, + {"name": "notes", "type": "text", "value": "No auton, no climb"}, + {"name": "algae_scored_in_net", "type": "integer", "value": 3}, + ], + [ + {"name": "team_number", "type": "large_integer", "value": "5"}, + {"name": "match_number", "type": "large_integer", "value": "1"}, + {"name": "auton_moved", "type": "boolean", "value": True}, + {"name": "feeder_pickup", "type": "boolean", "value": False}, + {"name": "park_in_barge_zone", "type": "boolean", "value": True}, + {"name": "climb_shallow", "type": "boolean", "value": False}, + {"name": "climb_deep", "type": "boolean", "value": False}, + {"name": "coral_levels", "type": "multiple_choice", "value": "Level 1"}, + {"name": "auton_algae_scored_in_processor", "type": "integer", "value": 1}, + {"name": "coral_scored", "type": "integer", "value": 2}, + {"name": "algae_scored_in_net", "type": "integer", "value": 1}, + {"name": "algae_scored_in_processor", "type": "integer", "value": 2}, + ], + [ + {"name": "team_number", "type": "large_integer", "value": "6"}, + {"name": "match_number", "type": "large_integer", "value": "1"}, + {"name": "auton_moved", "type": "boolean", "value": False}, + {"name": "feeder_pickup", "type": "boolean", "value": False}, + {"name": "park_in_barge_zone", "type": "boolean", "value": True}, + {"name": "climb_shallow", "type": "boolean", "value": False}, + {"name": "climb_deep", "type": "boolean", "value": False}, + {"name": "notes", "type": "text", "value": "Only net bot"}, + {"name": "algae_scored_in_net", "type": "integer", "value": 4}, + ], +] diff --git a/scouting/main/models.py b/scouting/main/models.py index 73e6312..057edb0 100644 --- a/scouting/main/models.py +++ b/scouting/main/models.py @@ -1,6 +1,7 @@ from django.db import models -YEARS = [(2024, "2024")] +YEARS = [(2024, "2024"), (2025, "2025")] + # Stores individual contributed data for each year and event class Data(models.Model): @@ -10,7 +11,9 @@ class Data(models.Model): event_code = models.CharField(max_length=99) data = models.JSONField(default=dict, blank=True) created = models.DateTimeField(null=True, blank=True) - event_model = models.ForeignKey("Event", on_delete=models.SET_NULL, null=True, blank=True) + event_model = models.ForeignKey( + "Event", on_delete=models.SET_NULL, null=True, blank=True + ) def __str__(self): return f"Data from {self.event} in {str(self.year)}" @@ -18,6 +21,7 @@ def __str__(self): class Meta: verbose_name_plural = "Data" + class Event(models.Model): year = models.IntegerField(choices=YEARS) name = models.CharField(max_length=999) @@ -30,4 +34,4 @@ def __str__(self): if self.custom: return f"{self.name} in {str(self.year)} (Custom event)" else: - return f"{self.name} in {str(self.year)}" \ No newline at end of file + return f"{self.name} in {str(self.year)}" diff --git a/scouting/main/season_fields.py b/scouting/main/season_fields.py index 0432bbf..e80309b 100644 --- a/scouting/main/season_fields.py +++ b/scouting/main/season_fields.py @@ -158,3 +158,263 @@ ], }, ] + +reefscape = [ + { + "section": "Main", + "simple_name": "main", + "fields": [ + { + "name": "Team Number", + "simple_name": "team_number", + "type": "large_integer", + "required": True, + }, + { + "name": "Match Number", + "simple_name": "match_number", + "type": "large_integer", + "required": True, + }, + ], + }, + { + "section": "Auton", + "simple_name": "auton", + "fields": [ + { + "name": "Coral Scored in Reef", + "simple_name": "auton_coral_scored", + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 20, + "required": False, + }, + { + "name": "Algae Scored in Net", + "simple_name": "auton_algae_scored_in_net", + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 20, + "required": False, + }, + { + "name": "Algae Scored in Processor", + "simple_name": "auton_algae_scored_in_processor", + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 20, + "required": False, + }, + ], + }, + { + "section": "Teleop", + "simple_name": "teleop", + "fields": [ + { + "name": "Coral Scored in Reef", + "simple_name": "coral_scored", + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 20, + "required": False, + }, + { + "name": "Algae Scored in Net", + "simple_name": "algae_scored_in_net", + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 20, + "required": False, + }, + { + "name": "Algae Scored in Processor", + "simple_name": "algae_scored_in_processor", + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 20, + "required": False, + }, + ], + }, + # TODO: These fields could be used if more information might want to be collected + # { + # "section": "Teleop", + # "simple_name": "teleop", + # "fields": [ + # { + # "section": "Coral", + # "simple_name": "coral", + # "fields": [ + # { + # "name": "Coral Scored Level 1", + # "simple_name": "coral_scored_level_1", + # "type": "integer", + # "default": 0, + # "minimum": 0, + # "maximum": 20, + # "required": False, + # }, + # { + # "name": "Coral Scored Level 2", + # "simple_name": "coral_scored_level_2", + # "type": "integer", + # "default": 0, + # "minimum": 0, + # "maximum": 20, + # "required": False, + # }, + # { + # "name": "Coral Scored Level 3", + # "simple_name": "coral_scored_level_3", + # "type": "integer", + # "default": 0, + # "minimum": 0, + # "maximum": 20, + # "required": False, + # }, + # { + # "name": "Coral Scored Level 4", + # "simple_name": "coral_scored_level_4", + # "type": "integer", + # "default": 0, + # "minimum": 0, + # "maximum": 20, + # "required": False, + # }, + # { + # "name": "Coral Missed", + # "simple_name": "coral_missed", + # "type": "integer", + # "default": 0, + # "minimum": 0, + # "maximum": 20, + # "required": False, + # }, + # ], + # }, + # { + # "section": "Algae", + # "simple_name": "algae", + # "fields": [ + # { + # "section": "Net", + # "simple_name": "algae_net", + # "fields": [ + # { + # "name": "Algae Scored in Net", + # "simple_name": "algae_scored_in_net", + # "type": "integer", + # "default": 0, + # "minimum": 0, + # "maximum": 20, + # "required": False, + # }, + # { + # "name": "Algae Missed in Net", + # "simple_name": "algae_missed_in_net", + # "type": "integer", + # "default": 0, + # "minimum": 0, + # "maximum": 20, + # "required": False, + # }, + # ], + # }, + # { + # "section": "Processor", + # "simple_name": "algae_processor", + # "fields": [ + # { + # "name": "Algae Scored in Processor", + # "simple_name": "algae_scored_in_processor", + # "type": "integer", + # "default": 0, + # "minimum": 0, + # "maximum": 20, + # "required": False, + # }, + # { + # "name": "Algae Missed in Processor", + # "simple_name": "algae_missed_in_processor", + # "type": "integer", + # "default": 0, + # "minimum": 0, + # "maximum": 20, + # "required": False, + # }, + # ], + # }, + # ], + # }, + # ], + # }, + { + "section": "Extra Information", + "simple_name": "extra_information", + "fields": [ + { + "name": "Moved during Auton", + "simple_name": "auton_moved", + "type": "boolean", + "required": False, + }, + { + "name": "Coral Levels", + "simple_name": "coral_levels", + "type": "multiple_choice", + "choices": ["N/A", "Level 1", "Level 2", "Level 3", "Level 4"], + "required": False, + }, + { + "name": "Feeder Station Pickup", + "simple_name": "feeder_pickup", + "type": "boolean", + "required": False, + }, + { + "section": "Barge", + "simple_name": "barge", + "fields": [ + { + "name": "Park in Barge Zone", + "simple_name": "park_in_barge_zone", + "type": "boolean", + "required": False, + }, + { + "name": "Climb on Shallow Cage", + "simple_name": "climb_shallow", + "type": "boolean", + "required": False, + }, + { + "name": "Climb on Deep Cage", + "simple_name": "climb_deep", + "type": "boolean", + "required": False, + }, + ], + }, + ], + }, + { + "section": "Additional Notes", + "simple_name": "additional_notes", + "fields": [ + { + "name": "Additional Notes or Comments", + "simple_name": "notes", + "type": "text", + "required": False, + }, + ], + }, +] diff --git a/scouting/main/templates/base.html b/scouting/main/templates/base.html index 21aefdf..c193a7f 100644 --- a/scouting/main/templates/base.html +++ b/scouting/main/templates/base.html @@ -37,6 +37,7 @@ {% block body %}{% endblock %} {% include 'menu.html' %} + {% include "server_message.html" %} {% block scripts %}{% endblock %} diff --git a/scouting/main/templates/contribute.html b/scouting/main/templates/contribute.html index 4d0935d..b9710f3 100644 --- a/scouting/main/templates/contribute.html +++ b/scouting/main/templates/contribute.html @@ -460,11 +460,12 @@ if (this.check_fields()) { let report_uuid = crypto.randomUUID(); - const openRequest = indexedDB.open("scouting_data", 2); + const openRequest = indexedDB.open("scouting_data", 3); openRequest.onupgradeneeded = (event) => { const db = event.target.result; - const objectStore = db.createObjectStore("backups", { keyPath: "uuid" }); + db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("backups", { keyPath: "uuid" }); }; openRequest.onsuccess = (event) => { @@ -522,11 +523,12 @@ } else { console.log("You're offline, report will be saved offline.") - const openRequest = indexedDB.open("scouting_data", 2); + const openRequest = indexedDB.open("scouting_data", 3); openRequest.onupgradeneeded = (event) => { const db = event.target.result; - const objectStore = db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("backups", { keyPath: "uuid" }); }; openRequest.onsuccess = (event) => { diff --git a/scouting/main/templates/index.html b/scouting/main/templates/index.html index fa50a3d..5e4e848 100644 --- a/scouting/main/templates/index.html +++ b/scouting/main/templates/index.html @@ -281,6 +281,8 @@ }, next() { + this.selected_year = this.$refs.year.value; + window.dispatchEvent(new CustomEvent('header_year', { detail: { year: this.selected_year } })); @@ -288,7 +290,12 @@ }, init() { - this.get_year_data(); + setTimeout(() => { + this.selected_year = this.$refs.year.value; + + this.get_year_data(); + }, 100) + } }; } @@ -545,7 +552,6 @@ }); window.addEventListener('header_year', (event) => { - event.stopImmediatePropagation(); const { year } = event.detail; this.year = year; diff --git a/scouting/main/templates/menu/main.html b/scouting/main/templates/menu/main.html index 8d4028d..376bd01 100644 --- a/scouting/main/templates/menu/main.html +++ b/scouting/main/templates/menu/main.html @@ -115,11 +115,12 @@ check_for_offline_reports() { if (globalThis.offline == false) { - const openRequest = indexedDB.open("scouting_data", 2); + const openRequest = indexedDB.open("scouting_data", 3); openRequest.onupgradeneeded = (event) => { const db = event.target.result; - const objectStore = db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("backups", { keyPath: "uuid" }); }; openRequest.onsuccess = (event) => { @@ -154,11 +155,12 @@ }, upload_offline_reports() { - const openRequest = indexedDB.open("scouting_data", 2); + const openRequest = indexedDB.open("scouting_data", 3); openRequest.onupgradeneeded = (event) => { const db = event.target.result; - const objectStore = db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("backups", { keyPath: "uuid" }); }; openRequest.onsuccess = (event) => { diff --git a/scouting/main/templates/menu/report_backups.html b/scouting/main/templates/menu/report_backups.html index ca65166..a6e0f68 100644 --- a/scouting/main/templates/menu/report_backups.html +++ b/scouting/main/templates/menu/report_backups.html @@ -8,11 +8,12 @@ backed_up_reports_confirm_delete: false, get_local_backup_reports() { - const openRequest = indexedDB.open("scouting_data", 2); + const openRequest = indexedDB.open("scouting_data", 3); openRequest.onupgradeneeded = (event) => { const db = event.target.result; - const objectStore = db.createObjectStore("backups", { keyPath: "uuid" }); + db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("backups", { keyPath: "uuid" }); }; openRequest.onsuccess = (event) => { @@ -38,11 +39,12 @@ }, async check_local_backup_reports() { - const openRequest = indexedDB.open("scouting_data", 2); + const openRequest = indexedDB.open("scouting_data", 3); openRequest.onupgradeneeded = (event) => { const db = event.target.result; - const objectStore = db.createObjectStore("backups", { keyPath: "uuid" }); + db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("backups", { keyPath: "uuid" }); }; openRequest.onsuccess = (event) => { @@ -99,11 +101,12 @@ }, clear_saved_backup_reports() { - const openRequest = indexedDB.open("scouting_data", 2); + const openRequest = indexedDB.open("scouting_data", 3); openRequest.onupgradeneeded = (event) => { const db = event.target.result; - const objectStore = db.createObjectStore("backups", { keyPath: "uuid" }); + db.createObjectStore("offline_reports", { keyPath: "uuid" }); + db.createObjectStore("backups", { keyPath: "uuid" }); }; openRequest.onsuccess = (event) => { diff --git a/scouting/main/templates/server_message.html b/scouting/main/templates/server_message.html new file mode 100644 index 0000000..a0dcc09 --- /dev/null +++ b/scouting/main/templates/server_message.html @@ -0,0 +1,32 @@ +
+
+ +
+

Server Message

+ +
+ +

+
+
+ + diff --git a/scouting/main/views.py b/scouting/main/views.py index 0f92162..2b6c8dc 100644 --- a/scouting/main/views.py +++ b/scouting/main/views.py @@ -14,7 +14,7 @@ from urllib.parse import unquote # TODO: This is a duplicate of a similar array in models.py, I don't know if there's a good way to make these into one array -YEARS = ["2024"] +YEARS = ["2024", "2025"] DATE_FORMAT = "%Y-%m-%d" @@ -23,6 +23,8 @@ def get_season_data_from_year(year): if year == "2024": return season_fields.crescendo + elif year == "2025": + return season_fields.reefscape else: return None @@ -31,6 +33,8 @@ def get_season_data_from_year(year): def get_demo_data_from_year(year): if year == "2024": return demo_data.crescendo + elif year == "2025": + return demo_data.reefscape else: return None @@ -40,6 +44,7 @@ def index(request): "SERVER_IP": settings.SERVER_IP, "TBA_API_KEY": settings.TBA_API_KEY, "YEARS": json.dumps(YEARS), + "SERVER_MESSAGE": settings.SERVER_MESSAGE, } return render(request, "index.html", context) @@ -56,6 +61,7 @@ def contribute(request): context = { "SERVER_IP": settings.SERVER_IP, "TBA_API_KEY": settings.TBA_API_KEY, + "SERVER_MESSAGE": settings.SERVER_MESSAGE, "season_fields": json.dumps( get_season_data_from_year(request.GET.get("year", "unknown")) ), @@ -81,6 +87,7 @@ def data(request): context = { "SERVER_IP": settings.SERVER_IP, "TBA_API_KEY": settings.TBA_API_KEY, + "SERVER_MESSAGE": settings.SERVER_MESSAGE, "username": request.GET.get("username", "unknown"), "event_name": request.GET.get("event_name", "unknown"), "event_code": request.GET.get("event_code", "unknown"), @@ -105,6 +112,7 @@ def submit(request): name=unquote(request.headers["event_name"]), event_code=request.headers["event_code"], custom=True, + year=request.headers["year"], ) data = Data( @@ -120,7 +128,9 @@ def submit(request): return HttpResponse(request, "Success") else: - events = Event.objects.filter(event_code=request.headers["event_code"]) + events = Event.objects.filter( + event_code=request.headers["event_code"], year=request.headers["year"] + ) if len(events) == 0: event = Event( year=request.headers["year"], @@ -175,11 +185,15 @@ def get_data(request): name=unquote(request.headers["event_name"]), event_code=request.headers["event_code"], custom=True, + year=request.headers["year"], ) event = events[0] else: - events = Event.objects.filter(event_code=request.headers["event_code"]) + events = Event.objects.filter( + event_code=request.headers["event_code"], + year=request.headers["year"], + ) if len(events) == 0: event = Event( year=request.headers["year"], @@ -192,6 +206,9 @@ def get_data(request): else: event = events[0] + print(event) + print(event.year) + data = Data.objects.filter( year=request.headers["year"], event=unquote(request.headers["event_name"]), diff --git a/scouting/scouting/settings.py b/scouting/scouting/settings.py index 9b01277..5ce34d1 100644 --- a/scouting/scouting/settings.py +++ b/scouting/scouting/settings.py @@ -15,7 +15,8 @@ import environ -env = environ.Env(interpolate=True, +env = environ.Env( + interpolate=True, DEBUG=(bool, False), SECRET_KEY=( str, @@ -23,7 +24,7 @@ ), DJANGO_ALLOWED_HOSTS=(list, ["localhost", "127.0.0.1"]), ADMIN_PATH=(str, "admin/"), - DATABASE_URL=(str, "sqlite:///db.sqlite3") + DATABASE_URL=(str, "sqlite:///db.sqlite3"), ) # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -44,48 +45,49 @@ ADMIN_PATH = env("ADMIN_PATH") TBA_API_KEY = env("TBA_API_KEY") SERVER_IP = env("SERVER_IP") +SERVER_MESSAGE = env("SERVER_MESSAGE") # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'main' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "main", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'scouting.urls' +ROOT_URLCONF = "scouting.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'scouting.wsgi.application' +WSGI_APPLICATION = "scouting.wsgi.application" # Database @@ -101,16 +103,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -118,9 +120,9 @@ # Internationalization # https://docs.djangoproject.com/en/5.1/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -138,4 +140,4 @@ # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/scouting/static/main/styles.css b/scouting/static/main/styles.css index 6ea3416..e000351 100644 --- a/scouting/static/main/styles.css +++ b/scouting/static/main/styles.css @@ -579,6 +579,10 @@ video { left: 0px; } +.left-1\/2 { + left: 50%; +} + .right-2 { right: 0.5rem; } @@ -591,6 +595,10 @@ video { top: 0.5rem; } +.top-3 { + top: 0.75rem; +} + .z-10 { z-index: 10; } @@ -797,6 +805,10 @@ video { max-width: 1rem; } +.max-w-\[95vw\] { + max-width: 95vw; +} + .max-w-sm { max-width: 24rem; } @@ -805,6 +817,15 @@ video { table-layout: auto; } +.-translate-x-1\/2 { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + @keyframes pulse { 50% { opacity: .5; @@ -954,6 +975,11 @@ video { border-color: rgb(249 115 22 / var(--tw-border-opacity)); } +.border-purple-500 { + --tw-border-opacity: 1; + border-color: rgb(168 85 247 / var(--tw-border-opacity)); +} + .border-slate-300 { --tw-border-opacity: 1; border-color: rgb(203 213 225 / var(--tw-border-opacity)); @@ -969,11 +995,6 @@ video { border-color: rgb(51 65 85 / var(--tw-border-opacity)); } -.border-purple-500 { - --tw-border-opacity: 1; - border-color: rgb(168 85 247 / var(--tw-border-opacity)); -} - .border-opacity-80 { --tw-border-opacity: 0.8; } @@ -1126,6 +1147,10 @@ video { text-align: center; } +.align-middle { + vertical-align: middle; +} + .font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } @@ -1613,6 +1638,10 @@ video { border-color: rgb(194 65 12 / var(--tw-border-opacity)); } +.dark\:border-purple-900\/80:where(.dark, .dark *) { + border-color: rgb(88 28 135 / 0.8); +} + .dark\:border-slate-600:where(.dark, .dark *) { --tw-border-opacity: 1; border-color: rgb(71 85 105 / var(--tw-border-opacity)); @@ -1623,15 +1652,6 @@ video { border-color: rgb(51 65 85 / var(--tw-border-opacity)); } -.dark\:border-purple-700:where(.dark, .dark *) { - --tw-border-opacity: 1; - border-color: rgb(126 34 206 / var(--tw-border-opacity)); -} - -.dark\:border-purple-900\/80:where(.dark, .dark *) { - border-color: rgb(88 28 135 / 0.8); -} - .dark\:bg-green-800\/70:where(.dark, .dark *) { background-color: rgb(22 101 52 / 0.7); }