unique constraint for only one hostmask enabled at a time

this replaces the need for a game+hostmask unique constraint, with this
change, a player can only have one active character they're playing
with anyway, regardless of how many games there are

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
Brian S. Stephan 2024-05-14 11:13:13 -05:00
parent d0531bff53
commit 2ad79285b3
Signed by: bss
GPG Key ID: 3DE06D3180895FCB
3 changed files with 52 additions and 1 deletions

View File

@ -0,0 +1,26 @@
# Generated by Django 5.0.5 on 2024-05-14 16:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('idlerpg', '0006_alter_character_status'),
]
operations = [
migrations.RemoveConstraint(
model_name='character',
name='one_player_character_per_game',
),
migrations.AddField(
model_name='character',
name='enabled',
field=models.BooleanField(default=True),
),
migrations.AddConstraint(
model_name='character',
constraint=models.UniqueConstraint(condition=models.Q(('enabled', True)), fields=('hostmask',), name='one_enabled_character_at_a_time'),
),
]

View File

@ -95,6 +95,7 @@ class Character(models.Model):
password = models.CharField(max_length=256) password = models.CharField(max_length=256)
hostmask = models.CharField(max_length=256) hostmask = models.CharField(max_length=256)
status = models.CharField(max_length=8, choices=CHARACTER_STATUSES, default='OFFLINE') status = models.CharField(max_length=8, choices=CHARACTER_STATUSES, default='OFFLINE')
enabled = models.BooleanField(default=True)
character_class = models.CharField(max_length=30) character_class = models.CharField(max_length=30)
level = models.PositiveIntegerField(default=0) level = models.PositiveIntegerField(default=0)
@ -119,7 +120,8 @@ class Character(models.Model):
"""Options for the Character and its objects.""" """Options for the Character and its objects."""
constraints = [ constraints = [
models.UniqueConstraint("game", "hostmask", name="one_player_character_per_game"), models.UniqueConstraint(fields=['hostmask'], condition=models.Q(enabled=True),
name='one_enabled_character_at_a_time'),
] ]
def __str__(self): def __str__(self):

View File

@ -7,6 +7,8 @@ import logging
from datetime import timedelta from datetime import timedelta
from unittest.mock import patch from unittest.mock import patch
from django.db import transaction
from django.db.utils import IntegrityError
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone
@ -152,6 +154,27 @@ class CharacterTest(TestCase):
assert new_char.last_login == register_time assert new_char.last_login == register_time
assert new_char.password[0:13] == 'pbkdf2_sha256' assert new_char.password[0:13] == 'pbkdf2_sha256'
def test_cant_register_twice(self):
"""Test that we get a unique constraint error if we try to make a second enabled character."""
game = Game.objects.get(pk=1)
register_time = timezone.now()
with patch('django.utils.timezone.now', return_value=register_time):
new_char = Character.objects.register('new1', game, 'pass', 'bss!bss@test_double_register', 'unit tester')
with self.assertRaises(IntegrityError):
with transaction.atomic():
Character.objects.register('new2', game, 'pass', 'bss!bss@test_double_register', 'unit tester')
# try again with the first character un-enabled
new_char.enabled = False
new_char.save()
register_time = timezone.now()
with patch('django.utils.timezone.now', return_value=register_time):
newer_char = Character.objects.register('new2', game, 'pass', 'bss!bss@test_double_register', 'unit tester')
assert new_char.hostmask == newer_char.hostmask
def test_level_up(self): def test_level_up(self):
"""Test the level up functionality.""" """Test the level up functionality."""
char = Character.objects.get(pk=1) char = Character.objects.get(pk=1)