From a52d75af4f3e9109354d7eceeb59f24eff27c189 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 14:34:58 -0500 Subject: [PATCH 01/11] online and last_online fields to Player model online is a boolean representing if the player is currently online or not last_online is a datetime, which is set when the Player is created, and each time they are queried as online on a server. --- raptorWeb/gameservers/models.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/raptorWeb/gameservers/models.py b/raptorWeb/gameservers/models.py index 6e3c03a8..db58c0a9 100644 --- a/raptorWeb/gameservers/models.py +++ b/raptorWeb/gameservers/models.py @@ -338,7 +338,7 @@ class Meta: class Player(models.Model): """ - Playesr that are currently logged in to a Server + Players that have joined a server at some point. """ server = models.ForeignKey( Server, @@ -348,6 +348,13 @@ class Player(models.Model): name = models.CharField( max_length=50, unique=True) + + online = models.BooleanField( + default=False) + + last_online = models.DateTimeField( + verbose_name="Last Online", + auto_now_add=True) def __str__(self): return self.name From d506d16084981fc2fc8e038848f52a0aaae41932 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 14:35:20 -0500 Subject: [PATCH 02/11] Player migrations --- .../migrations/0004_player_online.py | 18 ++++++++++++++++++ .../migrations/0005_player_last_online.py | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 raptorWeb/gameservers/migrations/0004_player_online.py create mode 100644 raptorWeb/gameservers/migrations/0005_player_last_online.py diff --git a/raptorWeb/gameservers/migrations/0004_player_online.py b/raptorWeb/gameservers/migrations/0004_player_online.py new file mode 100644 index 00000000..0cfd687e --- /dev/null +++ b/raptorWeb/gameservers/migrations/0004_player_online.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2023-11-06 18:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gameservers', '0003_server_archived'), + ] + + operations = [ + migrations.AddField( + model_name='player', + name='online', + field=models.BooleanField(default=False), + ), + ] diff --git a/raptorWeb/gameservers/migrations/0005_player_last_online.py b/raptorWeb/gameservers/migrations/0005_player_last_online.py new file mode 100644 index 00000000..e62956c0 --- /dev/null +++ b/raptorWeb/gameservers/migrations/0005_player_last_online.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.7 on 2023-11-06 18:35 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gameservers', '0004_player_online'), + ] + + operations = [ + migrations.AddField( + model_name='player', + name='last_online', + field=models.DateTimeField(auto_now_add=True, verbose_name='Last Online'), + ), + ] From 32799d0aff12c8d4445b1f6c261a82741c5c64b0 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 14:35:41 -0500 Subject: [PATCH 03/11] Show new fields in Admin as readonly --- raptorWeb/gameservers/admin.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/raptorWeb/gameservers/admin.py b/raptorWeb/gameservers/admin.py index b8f85d6e..2caf1a56 100644 --- a/raptorWeb/gameservers/admin.py +++ b/raptorWeb/gameservers/admin.py @@ -70,20 +70,24 @@ class PlayerAdmin(admin.ModelAdmin): ('Player Information', { 'fields': ( 'server', - 'name') + 'name', + 'online', + 'last_online') }), ) readonly_fields: tuple[str] = ( 'server', - 'name' + 'name', + 'online', + 'last_online' ) search_fields: list[str] = [ 'name', ] - list_display: list[str] = ['name', 'server'] + list_display: list[str] = ['name', 'server', 'online', 'last_online'] def has_add_permission(self, request, obj=None): return False From 456c61878a0aaab6cc2658003dd71c499308555e Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 14:36:01 -0500 Subject: [PATCH 04/11] Player_List queryset only get online players --- raptorWeb/gameservers/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raptorWeb/gameservers/views.py b/raptorWeb/gameservers/views.py index 7e586ede..dd794e57 100644 --- a/raptorWeb/gameservers/views.py +++ b/raptorWeb/gameservers/views.py @@ -43,6 +43,9 @@ class Player_List(ListView): the Server Manager. """ model: Player = Player + + def get_queryset(self) -> Player.objects: + return Player.objects.filter(online=True) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) From 62fbe28d310fb56643d51d1ef05adedb21996aff Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 14:42:06 -0500 Subject: [PATCH 05/11] Retain players in database during server query Modify PlayerPoller to no longer clear players on every run. Instead, each online player on a server will be checked against the current Players in the database. If they exist, their current server, last online time, and online values will be changed and saved. If a Player does not exist, a new one will be created with the above data. As PlayerPoller queries all servers, a list of all online player names is saved. After all servers are queried, this list of online players is checked against the database. If any Players in the database's online value is True, but they are NOT in the online player list, they will be set to offline. This way, all players that have joined a server are saved, along with their last online time, and their current online status. This will be expanded upon further to incorporate statistics of players over time on each server. --- raptorWeb/gameservers/models.py | 42 +++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/raptorWeb/gameservers/models.py b/raptorWeb/gameservers/models.py index db58c0a9..1d401aac 100644 --- a/raptorWeb/gameservers/models.py +++ b/raptorWeb/gameservers/models.py @@ -4,7 +4,7 @@ from typing import Optional from django.db import models -from django.utils.timezone import localtime +from django.utils.timezone import localtime, now from django.conf import settings from mcstatus import JavaServer @@ -42,6 +42,9 @@ def _update_announcement_count(server: Server) -> None: server=server ).count() + all_players = Player.objects.all() + online_players = [] + if do_query == True: try: serverJSON: QueryResponse = JavaServer( @@ -53,15 +56,33 @@ def _update_announcement_count(server: Server) -> None: server.server_state = True _update_announcement_count(server) server.save() - [Player.objects.create( - name=player, - server=server).save() for player in serverJSON.players.names] + + for player in serverJSON.players.names: + checked_player = all_players.filter(name=player).first() + # If a Player exists, update their information + if checked_player is not None: + online_players.append(checked_player.name) + checked_player.server = server + checked_player.online = True + checked_player.last_online = now() + checked_player.save() + # If not, create a new Player + else: + online_players.append(player) + new_player = Player.objects.create( + name=player, + server=server, + online=True + ) + new_player.save() except TimeoutError: _set_offline_server(server) else: _set_offline_server(server) + + return online_players def poll_servers(self, servers: list['Server'], statistic_model: 'ServerStatistic') -> None: if statistic_model.time_last_polled == None: @@ -72,8 +93,8 @@ def poll_servers(self, servers: list['Server'], statistic_model: 'ServerStatisti if minutes_since_poll > 1 or self._has_run == False: statistic_model.total_player_count = 0 - Player.objects.all().delete() - + + all_online_players = [] for server in servers: if (server.server_address == "Default" or server.in_maintenance == True @@ -83,8 +104,15 @@ def poll_servers(self, servers: list['Server'], statistic_model: 'ServerStatisti do_query = False) else: - self._query_and_update_server(server) + online_players = self._query_and_update_server(server) + all_online_players.extend(online_players) statistic_model.total_player_count += server.player_count + + # Mark players who were not queried as online, but are marked as online in the database, as offline. + newly_offline_players = Player.objects.filter(online=True).exclude(name__in=all_online_players) + for player in newly_offline_players: + player.online = False + player.save() self._has_run = True statistic_model.time_last_polled = localtime() From 430260370642a571f1c43cada7850bd0128c56a1 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 14:59:59 -0500 Subject: [PATCH 06/11] Do not delete players when servers are deleted --- raptorWeb/gameservers/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raptorWeb/gameservers/models.py b/raptorWeb/gameservers/models.py index 1d401aac..ec2c0d50 100644 --- a/raptorWeb/gameservers/models.py +++ b/raptorWeb/gameservers/models.py @@ -370,8 +370,8 @@ class Player(models.Model): """ server = models.ForeignKey( Server, - default=0, - on_delete=models.CASCADE) + default=0, + on_delete=models.PROTECT) name = models.CharField( max_length=50, From 0e42dfb4c241bd02c951d46ca7366e8cd6188b9f Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 15:00:45 -0500 Subject: [PATCH 07/11] Model to track player count at a point in time This model exists to track player counts on a specific server at a point in time. --- raptorWeb/gameservers/models.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/raptorWeb/gameservers/models.py b/raptorWeb/gameservers/models.py index ec2c0d50..68dd5213 100644 --- a/raptorWeb/gameservers/models.py +++ b/raptorWeb/gameservers/models.py @@ -396,3 +396,35 @@ def get_server(self): class Meta: verbose_name = "Player" verbose_name_plural = "Players" + + +class PlayerCountHistoric(models.Model): + """ + The total count of players on a server at a + specific point in time. These are created each + time servers are queried, for each server. + """ + server = models.ForeignKey( + Server, + default=0, + on_delete=models.CASCADE) + + player_count = models.IntegerField( + verbose_name="Players Online") + + checked_time = models.DateTimeField( + verbose_name="Time of Query", + auto_now_add=True) + + def __str__(self): + return self.name + + def get_player_count(self): + return self.player_count + + def get_server(self): + return self.server + + class Meta: + verbose_name = "Historic Player Count" + verbose_name_plural = "Historic Player Counts" From fe7cb7bb86b93e61c29e5e03b5e6cd3b571d05f8 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 15:00:55 -0500 Subject: [PATCH 08/11] prev 2 commits migrations --- ...alter_player_server_playercounthistoric.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 raptorWeb/gameservers/migrations/0006_alter_player_server_playercounthistoric.py diff --git a/raptorWeb/gameservers/migrations/0006_alter_player_server_playercounthistoric.py b/raptorWeb/gameservers/migrations/0006_alter_player_server_playercounthistoric.py new file mode 100644 index 00000000..8b6734b9 --- /dev/null +++ b/raptorWeb/gameservers/migrations/0006_alter_player_server_playercounthistoric.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.7 on 2023-11-06 19:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gameservers', '0005_player_last_online'), + ] + + operations = [ + migrations.AlterField( + model_name='player', + name='server', + field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.PROTECT, to='gameservers.server'), + ), + migrations.CreateModel( + name='PlayerCountHistoric', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('player_count', models.IntegerField(verbose_name='Players Online')), + ('checked_time', models.DateTimeField(auto_now_add=True, verbose_name='Time of Query')), + ('server', models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='gameservers.server')), + ], + options={ + 'verbose_name': 'Historic Player Count', + 'verbose_name_plural': 'Historic Player Counts', + }, + ), + ] From 0c9d094e5f96308476f04abbe0666cfecec989d2 Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 15:13:49 -0500 Subject: [PATCH 09/11] PlayerCountHistoric admin definitions --- raptorWeb/gameservers/admin.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/raptorWeb/gameservers/admin.py b/raptorWeb/gameservers/admin.py index 2caf1a56..d2e4ff45 100644 --- a/raptorWeb/gameservers/admin.py +++ b/raptorWeb/gameservers/admin.py @@ -3,7 +3,7 @@ from tinymce.widgets import TinyMCE -from raptorWeb.gameservers.models import Server, Player, ServerStatistic +from raptorWeb.gameservers.models import Server, Player, ServerStatistic, PlayerCountHistoric class ServerAdminForm(ModelForm): class Meta: @@ -94,6 +94,34 @@ def has_add_permission(self, request, obj=None): def has_change_permission(self, request, obj=None): return False + +class PlayerCountHistoricAdmin(admin.ModelAdmin): + """ + Object defining behavior and display of + PlayerCountHistoric in the Django admin interface. + """ + fieldsets: tuple[tuple[str, dict[str, tuple[str]]]] = ( + ('Player Information', { + 'fields': ( + 'server', + 'player_count', + 'checked_time') + }), + ) + + readonly_fields: tuple[str] = ( + 'server', + 'player_count', + 'checked_time' + ) + + list_display: list[str] = ['server', 'player_count', 'checked_time'] + + def has_add_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False class ServerStatisticAdmin(admin.ModelAdmin): """ @@ -121,4 +149,5 @@ def has_change_permission(self, request, obj=None): admin.site.register(Server, ServerAdmin) admin.site.register(Player, PlayerAdmin) +admin.site.register(PlayerCountHistoric, PlayerCountHistoricAdmin) admin.site.register(ServerStatistic, ServerStatisticAdmin) From 12abeed5e61fffc55e13a048a79e0065c124219b Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 15:14:11 -0500 Subject: [PATCH 10/11] Remove str dunder method from PCH --- raptorWeb/gameservers/models.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/raptorWeb/gameservers/models.py b/raptorWeb/gameservers/models.py index 68dd5213..dee50809 100644 --- a/raptorWeb/gameservers/models.py +++ b/raptorWeb/gameservers/models.py @@ -415,9 +415,6 @@ class PlayerCountHistoric(models.Model): checked_time = models.DateTimeField( verbose_name="Time of Query", auto_now_add=True) - - def __str__(self): - return self.name def get_player_count(self): return self.player_count From 5d820e6f08e02cbb4937576c2b1cdf0e3081af9a Mon Sep 17 00:00:00 2001 From: zediious Date: Mon, 6 Nov 2023 15:14:55 -0500 Subject: [PATCH 11/11] PlayerCountHistoric each query for each server Each time a server is queried, a PlayerCountHistoric is made with the server and current count of players for that server added as fields. This will be draw charts. --- raptorWeb/gameservers/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raptorWeb/gameservers/models.py b/raptorWeb/gameservers/models.py index dee50809..29264cac 100644 --- a/raptorWeb/gameservers/models.py +++ b/raptorWeb/gameservers/models.py @@ -106,6 +106,10 @@ def poll_servers(self, servers: list['Server'], statistic_model: 'ServerStatisti else: online_players = self._query_and_update_server(server) all_online_players.extend(online_players) + PlayerCountHistoric.objects.create( + server=server, + player_count=server.player_count + ) statistic_model.total_player_count += server.player_count # Mark players who were not queried as online, but are marked as online in the database, as offline.