Merge pull request #3179 from volundmush/player_character_management

Removing direct access to Account.db._playable_characters in favor of…
This commit is contained in:
Griatch 2023-10-31 20:22:56 +01:00 committed by GitHub
commit 04e0852753
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 173 additions and 108 deletions

View file

@ -622,7 +622,7 @@ node_apply_character(caller, raw_string, **kwargs):
tmp_character = kwargs["tmp_character"] tmp_character = kwargs["tmp_character"]
new_character = tmp_character.apply(caller) new_character = tmp_character.apply(caller)
caller.account.db._playable_characters = [new_character] caller.account.add_character(new_character)
text = "Character created!" text = "Character created!"

View file

@ -311,12 +311,12 @@ Our rock-paper-scissor setup works like this:
- `defend` does nothing but has a chance to beat `hit`. - `defend` does nothing but has a chance to beat `hit`.
- `flee/disengage` must succeed two times in a row (i.e. not beaten by a `hit` once during the turn). If so the character leaves combat. - `flee/disengage` must succeed two times in a row (i.e. not beaten by a `hit` once during the turn). If so the character leaves combat.
```python ```python
# mygame/world/rules.py # mygame/world/rules.py
import random import random
# messages # messages
def resolve_combat(combat_handler, actiondict): def resolve_combat(combat_handler, actiondict):
@ -326,7 +326,7 @@ def resolve_combat(combat_handler, actiondict):
for each character: for each character:
{char.id:[(action1, char, target), (action2, char, target)], ...} {char.id:[(action1, char, target), (action2, char, target)], ...}
""" """
flee = {} # track number of flee commands per character flee = {} # track number of flee commands per character
for isub in range(2): for isub in range(2):
# loop over sub-turns # loop over sub-turns
messages = [] messages = []
@ -403,6 +403,7 @@ This is the last component we need, a command to initiate combat. This will tie
from evennia import create_script from evennia import create_script
class CmdAttack(Command): class CmdAttack(Command):
""" """
initiates combat initiates combat
@ -419,7 +420,7 @@ class CmdAttack(Command):
def func(self): def func(self):
"Handle command" "Handle command"
if not self.args: if not self.args:
self.caller.msg("Usage: attack <target>") self.caller.msg("Usage: attack <target>")
return return
target = self.caller.search(self.args) target = self.caller.search(self.args)
if not target: if not target:

View file

@ -206,7 +206,7 @@ def creating(request):
# create the character # create the character
char = create.create_object(typeclass=typeclass, key=name, char = create.create_object(typeclass=typeclass, key=name,
home=home, permissions=perms) home=home, permissions=perms)
user.db._playable_characters.append(char) user.add_character(char)
# add the right locks for the character so the account can # add the right locks for the character so the account can
# puppet it # puppet it
char.locks.add(" or ".join([ char.locks.add(" or ".join([
@ -290,7 +290,7 @@ def creating(request):
# create the character # create the character
char = create.create_object(typeclass=typeclass, key=name, char = create.create_object(typeclass=typeclass, key=name,
home=home, permissions=perms) home=home, permissions=perms)
user.db._playable_characters.append(char) user.add_character(char)
# add the right locks for the character so the account can # add the right locks for the character so the account can
# puppet it # puppet it
char.locks.add(" or ".join([ char.locks.add(" or ".join([

View file

@ -198,8 +198,8 @@ def index(request):
def index(request): def index(request):
"""The 'index' view.""" """The 'index' view."""
user = request.user user = request.user
if not user.is_anonymous() and user.db._playable_characters: if not user.is_anonymous() and user.characters:
character = user.db._playable_characters[0] character = user.characters[0]
``` ```
In this second case, it will select the first character of the account. In this second case, it will select the first character of the account.

View file

@ -120,6 +120,80 @@ class AccountSessionHandler(object):
return len(self.get()) return len(self.get())
class CharactersHandler:
"""
A simple Handler that lives on DefaultAccount as .characters via @lazy_property used to
wrap access to .db._playable_characters.
"""
def __init__(self, owner: "DefaultAccount"):
"""
Create the CharactersHandler.
Args:
owner: The Account that owns this handler.
"""
self.owner = owner
self._ensure_playable_characters()
self._clean()
def _ensure_playable_characters(self):
if self.owner.db._playable_characters is None:
self.owner.db._playable_characters = []
def _clean(self):
# Remove all instances of None from the list.
self.owner.db._playable_characters = [x for x in self.owner.db._playable_characters if x]
def add(self, character: "DefaultCharacter"):
"""
Add a character to this account's list of playable characters.
Args:
character (DefaultCharacter): The character to add.
"""
self._clean()
if character not in self.owner.db._playable_characters:
self.owner.db._playable_characters.append(character)
self.owner.at_post_add_character(character)
def remove(self, character: "DefaultCharacter"):
"""
Remove a character from this account's list of playable characters.
Args:
character (DefaultCharacter): The character to remove.
"""
self._clean()
if character in self.owner.db._playable_characters:
self.owner.db._playable_characters.remove(character)
self.owner.at_post_remove_character(character)
def all(self) -> list["DefaultCharacter"]:
"""
Get all playable characters.
Returns:
list[DefaultCharacter]: All playable characters.
"""
self._clean()
return list(self.owner.db._playable_characters)
def count(self) -> int:
"""
Get the number of playable characters.
Returns:
int: The number of playable characters.
"""
return len(self.all())
__len__ = count
def __iter__(self):
return iter(self.all())
class DefaultAccount(AccountDB, metaclass=TypeclassBase): class DefaultAccount(AccountDB, metaclass=TypeclassBase):
""" """
This is the base Typeclass for all Accounts. Accounts represent This is the base Typeclass for all Accounts. Accounts represent
@ -219,22 +293,31 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
load_kwargs={"category": "option"}, load_kwargs={"category": "option"},
) )
# Do not make this a lazy property; the web UI will not refresh it! @lazy_property
@property
def characters(self): def characters(self):
# Get playable characters list return CharactersHandler(self)
objs = self.db._playable_characters or []
# Rebuild the list if legacy code left null values after deletion def at_post_add_character(self, character: "DefaultCharacter"):
try: """
if None in objs: Called after a character is added to this account's list of playable characters.
objs = [x for x in self.db._playable_characters if x]
self.db._playable_characters = objs
except Exception as e:
logger.log_trace(e)
logger.log_err(e)
return objs Use it to easily implement custom logic when a character is added to an account.
Args:
character (DefaultCharacter): The character that was added.
"""
pass
def at_post_remove_character(self, character: "DefaultAccount"):
"""
Called after a character is removed from this account's list of playable characters.
Use it to easily implement custom logic when a character is removed from an account.
Args:
character (DefaultCharacter): The character that was removed.
"""
pass
def uses_screenreader(self, session=None): def uses_screenreader(self, session=None):
""" """
@ -743,8 +826,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
) )
if character: if character:
# Update playable character list # Update playable character list
if character not in self.characters: self.characters.add(character)
self.db._playable_characters.append(character)
# We need to set this to have @ic auto-connect to this character # We need to set this to have @ic auto-connect to this character
self.db._last_puppet = character self.db._last_puppet = character
@ -1483,11 +1565,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
else: else:
# In this mode we don't auto-connect but by default end up at a character selection # In this mode we don't auto-connect but by default end up at a character selection
# screen. We execute look on the account. # screen. We execute look on the account.
# we make sure to clean up the _playable_characters list in case
# any was deleted in the interim.
self.db._playable_characters = [char for char in self.db._playable_characters if char]
self.msg( self.msg(
self.at_look(target=self.db._playable_characters, session=session), session=session self.at_look(target=self.characters, session=session), session=session
) )
def at_failed_login(self, session, **kwargs): def at_failed_login(self, session, **kwargs):
@ -1825,11 +1904,8 @@ class DefaultGuest(DefaultAccount):
be on the safe side. be on the safe side.
""" """
super().at_server_shutdown() super().at_server_shutdown()
characters = self.db._playable_characters for character in self.characters:
if characters: character.delete()
for character in characters:
if character:
character.delete()
def at_post_disconnect(self, **kwargs): def at_post_disconnect(self, **kwargs):
""" """
@ -1841,8 +1917,6 @@ class DefaultGuest(DefaultAccount):
""" """
super().at_post_disconnect() super().at_post_disconnect()
characters = self.db._playable_characters for character in self.characters:
for character in characters: character.delete()
if character:
character.delete()
self.delete() self.delete()

View file

@ -105,14 +105,14 @@ class TestDefaultGuest(BaseEvenniaTest):
def test_at_server_shutdown(self): def test_at_server_shutdown(self):
account, errors = DefaultGuest.create(ip=self.ip) account, errors = DefaultGuest.create(ip=self.ip)
self.char1.delete = MagicMock() self.char1.delete = MagicMock()
account.db._playable_characters = [self.char1] account.characters.add(self.char1)
account.at_server_shutdown() account.at_server_shutdown()
self.char1.delete.assert_called() self.char1.delete.assert_called()
def test_at_post_disconnect(self): def test_at_post_disconnect(self):
account, errors = DefaultGuest.create(ip=self.ip) account, errors = DefaultGuest.create(ip=self.ip)
self.char1.delete = MagicMock() self.char1.delete = MagicMock()
account.db._playable_characters = [self.char1] account.characters.add(self.char1)
account.at_post_disconnect() account.at_post_disconnect()
self.char1.delete.assert_called() self.char1.delete.assert_called()
@ -358,19 +358,19 @@ class TestAccountPuppetDeletion(BaseEvenniaTest):
def test_puppet_deletion(self): def test_puppet_deletion(self):
# Check for existing chars # Check for existing chars
self.assertFalse( self.assertFalse(
self.account.db._playable_characters, "Account should not have any chars by default." self.account.characters, "Account should not have any chars by default."
) )
# Add char1 to account's playable characters # Add char1 to account's playable characters
self.account.db._playable_characters.append(self.char1) self.account.characters.add(self.char1)
self.assertTrue(self.account.db._playable_characters, "Char was not added to account.") self.assertTrue(self.account.characters, "Char was not added to account.")
# See what happens when we delete char1. # See what happens when we delete char1.
self.char1.delete() self.char1.delete()
# Playable char list should be empty. # Playable char list should be empty.
self.assertFalse( self.assertFalse(
self.account.db._playable_characters, self.account.characters,
f"Playable character list is not empty! {self.account.db._playable_characters}", f"Playable character list is not empty! {self.account.characters}",
) )
@ -383,10 +383,20 @@ class TestDefaultAccountEv(BaseEvenniaTest):
def test_characters_property(self): def test_characters_property(self):
"test existence of None in _playable_characters Attr" "test existence of None in _playable_characters Attr"
self.account.db._playable_characters = [self.char1, None] self.account.db._playable_characters = [self.char1, None]
chars = self.account.characters self.assertEqual(self.account.characters.all(), [self.char1])
self.assertEqual(chars, [self.char1])
self.assertEqual(self.account.db._playable_characters, [self.char1]) self.assertEqual(self.account.db._playable_characters, [self.char1])
def test_add_character_to_playable_list(self):
self.assertEqual(self.account.characters.all(), [])
self.account.characters.add(self.char1)
self.assertEqual(self.account.characters.all(), [self.char1])
def test_remove_character_from_playable_list(self):
self.account.characters.add(self.char1)
self.assertEqual(self.account.characters.all(), [self.char1])
self.account.characters.remove(self.char1)
self.assertEqual(self.account.characters.all(), [])
def test_puppet_success(self): def test_puppet_success(self):
self.account.msg = MagicMock() self.account.msg = MagicMock()
with patch("evennia.accounts.accounts._MULTISESSION_MODE", 2): with patch("evennia.accounts.accounts._MULTISESSION_MODE", 2):

View file

@ -60,12 +60,7 @@ class MuxAccountLookCommand(COMMAND_DEFAULT_CLASS):
super().parse() super().parse()
playable = self.account.db._playable_characters playable = self.account.characters
if playable is not None:
# clean up list if character object was deleted in between
if None in playable:
playable = [character for character in playable if character]
self.account.db._playable_characters = playable
# store playable property # store playable property
if self.args: if self.args:
self.playable = dict((utils.to_str(char.key.lower()), char) for char in playable).get( self.playable = dict((utils.to_str(char.key.lower()), char) for char in playable).get(
@ -155,8 +150,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
if ( if (
not account.is_superuser not account.is_superuser
and not account.check_permstring("Developer") and not account.check_permstring("Developer")
and account.db._playable_characters and account.characters
and len(account.db._playable_characters) >= _MAX_NR_CHARACTERS and len(account.characters) >= _MAX_NR_CHARACTERS
): ):
plural = "" if _MAX_NR_CHARACTERS == 1 else "s" plural = "" if _MAX_NR_CHARACTERS == 1 else "s"
self.msg(f"You may only have a maximum of {_MAX_NR_CHARACTERS} character{plural}.") self.msg(f"You may only have a maximum of {_MAX_NR_CHARACTERS} character{plural}.")
@ -184,7 +179,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
"puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer);delete:id(%i) or" "puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer);delete:id(%i) or"
" perm(Admin)" % (new_character.id, account.id, account.id) " perm(Admin)" % (new_character.id, account.id, account.id)
) )
account.db._playable_characters.append(new_character) account.characters.add(new_character)
if desc: if desc:
new_character.db.desc = desc new_character.db.desc = desc
elif not new_character.db.desc: elif not new_character.db.desc:
@ -223,7 +218,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
# use the playable_characters list to search # use the playable_characters list to search
match = [ match = [
char char
for char in utils.make_iter(account.db._playable_characters) for char in utils.make_iter(account.characters)
if char.key.lower() == self.args.lower() if char.key.lower() == self.args.lower()
] ]
if not match: if not match:
@ -243,9 +238,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
# only take action # only take action
delobj = caller.ndb._char_to_delete delobj = caller.ndb._char_to_delete
key = delobj.key key = delobj.key
caller.db._playable_characters = [ caller.characters.remove(delobj)
pc for pc in caller.db._playable_characters if pc != delobj
]
delobj.delete() delobj.delete()
self.msg(f"Character '{key}' was permanently deleted.") self.msg(f"Character '{key}' was permanently deleted.")
logger.log_sec( logger.log_sec(
@ -314,13 +307,13 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
else: else:
# argument given # argument given
if account.db._playable_characters: if (playables := account.characters):
# look at the playable_characters list first # look at the playable_characters list first
character_candidates.extend( character_candidates.extend(
utils.make_iter( utils.make_iter(
account.search( account.search(
self.args, self.args,
candidates=account.db._playable_characters, candidates=playables,
search_object=True, search_object=True,
quiet=True, quiet=True,
) )

View file

@ -589,7 +589,7 @@ class TestAccount(BaseEvenniaCommandTest):
] ]
) )
def test_ooc_look(self, multisession_mode, auto_puppet, max_nr_chars, expected_result): def test_ooc_look(self, multisession_mode, auto_puppet, max_nr_chars, expected_result):
self.account.db._playable_characters = [self.char1] self.account.characters.add(self.char1)
self.account.unpuppet_all() self.account.unpuppet_all()
with self.settings(MULTISESSION=multisession_mode): with self.settings(MULTISESSION=multisession_mode):
@ -609,14 +609,14 @@ class TestAccount(BaseEvenniaCommandTest):
self.call(account.CmdOOC(), "", "You go OOC.", caller=self.account) self.call(account.CmdOOC(), "", "You go OOC.", caller=self.account)
def test_ic(self): def test_ic(self):
self.account.db._playable_characters = [self.char1] self.account.characters.add(self.char1)
self.account.unpuppet_object(self.session) self.account.unpuppet_object(self.session)
self.call( self.call(
account.CmdIC(), "Char", "You become Char.", caller=self.account, receiver=self.char1 account.CmdIC(), "Char", "You become Char.", caller=self.account, receiver=self.char1
) )
def test_ic__other_object(self): def test_ic__other_object(self):
self.account.db._playable_characters = [self.obj1] self.account.characters.add(self.obj1)
self.account.unpuppet_object(self.session) self.account.unpuppet_object(self.session)
self.call( self.call(
account.CmdIC(), "Obj", "You become Obj.", caller=self.account, receiver=self.obj1 account.CmdIC(), "Obj", "You become Obj.", caller=self.account, receiver=self.obj1
@ -670,7 +670,7 @@ class TestAccount(BaseEvenniaCommandTest):
# whether permissions are being checked # whether permissions are being checked
# Add char to account playable characters # Add char to account playable characters
self.account.db._playable_characters.append(self.char1) self.account.characters.add(self.char1)
# Try deleting as Developer # Try deleting as Developer
self.call( self.call(

View file

@ -54,7 +54,7 @@ class ContribCmdCharCreate(MuxAccountCommand):
session = self.session session = self.session
# only one character should be in progress at a time, so we check for WIPs first # only one character should be in progress at a time, so we check for WIPs first
in_progress = [chara for chara in account.db._playable_characters if chara.db.chargen_step] in_progress = [chara for chara in account.characters if chara.db.chargen_step]
if len(in_progress): if len(in_progress):
# we're continuing chargen for a WIP character # we're continuing chargen for a WIP character
@ -64,7 +64,7 @@ class ContribCmdCharCreate(MuxAccountCommand):
charmax = settings.MAX_NR_CHARACTERS charmax = settings.MAX_NR_CHARACTERS
if not account.is_superuser and ( if not account.is_superuser and (
account.db._playable_characters and len(account.db._playable_characters) >= charmax account.characters and len(account.characters) >= charmax
): ):
plural = "" if charmax == 1 else "s" plural = "" if charmax == 1 else "s"
self.msg(f"You may only create a maximum of {charmax} character{plural}.") self.msg(f"You may only create a maximum of {charmax} character{plural}.")
@ -90,7 +90,7 @@ class ContribCmdCharCreate(MuxAccountCommand):
) )
# initalize the new character to the beginning of the chargen menu # initalize the new character to the beginning of the chargen menu
new_character.db.chargen_step = "menunode_welcome" new_character.db.chargen_step = "menunode_welcome"
account.db._playable_characters.append(new_character) account.characters.add(new_character)
# set the menu node to start at to the character's last saved step # set the menu node to start at to the character's last saved step
startnode = new_character.db.chargen_step startnode = new_character.db.chargen_step

View file

@ -17,7 +17,7 @@ class TestCharacterCreator(BaseEvenniaCommandTest):
self.account.swap_typeclass(character_creator.ContribChargenAccount) self.account.swap_typeclass(character_creator.ContribChargenAccount)
def test_ooc_look(self): def test_ooc_look(self):
self.account.db._playable_characters = [self.char1] self.account.characters.add(self.char1)
self.account.unpuppet_all() self.account.unpuppet_all()
self.char1.db.chargen_step = "start" self.char1.db.chargen_step = "start"

View file

@ -316,7 +316,7 @@ def node_apply_character(caller, raw_string, **kwargs):
""" """
tmp_character = kwargs["tmp_character"] tmp_character = kwargs["tmp_character"]
new_character = tmp_character.apply(caller) new_character = tmp_character.apply(caller)
caller.db._playable_characters.append(new_character) caller.characters.add(new_character)
text = "Character created!" text = "Character created!"

View file

@ -1149,10 +1149,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
# sever the connection (important!) # sever the connection (important!)
if self.account: if self.account:
# Remove the object from playable characters list # Remove the object from playable characters list
if self in self.account.db._playable_characters: self.account.characters.remove(self)
self.account.db._playable_characters = [
x for x in self.account.db._playable_characters if x != self
]
for session in self.sessions.all(): for session in self.sessions.all():
self.account.unpuppet_object(session) self.account.unpuppet_object(session)
@ -2562,8 +2559,7 @@ class DefaultCharacter(DefaultObject):
obj.db.creator_ip = ip obj.db.creator_ip = ip
if account: if account:
obj.db.creator_id = account.id obj.db.creator_id = account.id
if obj not in account.characters: account.characters.add(obj)
account.db._playable_characters.append(obj)
# Add locks # Add locks
if not locks and account: if not locks and account:

View file

@ -98,16 +98,15 @@ def create_objects():
# Create the in-game god-character for account #1 and set # Create the in-game god-character for account #1 and set
# it to exist in Limbo. # it to exist in Limbo.
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
try: try:
superuser_character = ObjectDB.objects.get(id=1) superuser_character = ObjectDB.objects.get(id=1)
except ObjectDB.DoesNotExist: except ObjectDB.DoesNotExist:
superuser_character = create.create_object( superuser_character, errors = superuser.create_character(
character_typeclass, key=superuser.username, nohome=True key=superuser.username, nohome=True, description=_("This is User #1.")
) )
if errors:
raise Exception(str(errors))
superuser_character.db_typeclass_path = character_typeclass
superuser_character.db.desc = _("This is User #1.")
superuser_character.locks.add( superuser_character.locks.add(
"examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()" "examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()"
) )
@ -118,11 +117,6 @@ def create_objects():
superuser.attributes.add("_first_login", True) superuser.attributes.add("_first_login", True)
superuser.attributes.add("_last_puppet", superuser_character) superuser.attributes.add("_last_puppet", superuser_character)
try:
superuser.db._playable_characters.append(superuser_character)
except AttributeError:
superuser.db_playable_characters = [superuser_character]
room_typeclass = settings.BASE_ROOM_TYPECLASS room_typeclass = settings.BASE_ROOM_TYPECLASS
try: try:
limbo_obj = ObjectDB.objects.get(id=2) limbo_obj = ObjectDB.objects.get(id=2)

View file

@ -647,9 +647,8 @@ class Evennia:
for guest in AccountDB.objects.all().filter( for guest in AccountDB.objects.all().filter(
db_typeclass_path=settings.BASE_GUEST_TYPECLASS db_typeclass_path=settings.BASE_GUEST_TYPECLASS
): ):
for character in guest.db._playable_characters: for character in guest.characters:
if character: character.delete()
character.delete()
guest.delete() guest.delete()
for mod in SERVER_STARTSTOP_MODULES: for mod in SERVER_STARTSTOP_MODULES:
if hasattr(mod, "at_server_cold_start"): if hasattr(mod, "at_server_cold_start"):

View file

@ -444,3 +444,4 @@ class ServerSession(_BASE_SESSION_CLASS):
return self.account.get_display_name(*args, **kwargs) return self.account.get_display_name(*args, **kwargs)
else: else:
return f"{self.protocol_key}({self.address})" return f"{self.protocol_key}({self.address})"

View file

@ -72,7 +72,6 @@ from evennia.utils.utils import to_str
MXP_ENABLED = settings.MXP_ENABLED MXP_ENABLED = settings.MXP_ENABLED
# ANSI definitions # ANSI definitions
ANSI_BEEP = "\07" ANSI_BEEP = "\07"

View file

@ -2941,3 +2941,4 @@ def str2int(number):
# invalid number-word, raise ValueError # invalid number-word, raise ValueError
raise ValueError(f"String {original_input} cannot be converted to int.") raise ValueError(f"String {original_input} cannot be converted to int.")
return sum(sums) return sum(sums)

View file

@ -310,7 +310,7 @@ class ObjectAdmin(admin.ModelAdmin):
This will: This will:
- Set account.db._last_puppet to this object - Set account.db._last_puppet to this object
- Add object to account.db._playable_characters - Add object to account.characters
- Change object locks to allow puppeting by account - Change object locks to allow puppeting by account
""" """
@ -319,10 +319,7 @@ class ObjectAdmin(admin.ModelAdmin):
if account: if account:
account.db._last_puppet = obj account.db._last_puppet = obj
if not account.db._playable_characters: account.characters.add(obj)
account.db._playable_characters = []
if obj not in account.db._playable_characters:
account.db._playable_characters.append(obj)
if not obj.access(account, "puppet"): if not obj.access(account, "puppet"):
lock = obj.locks.get("puppet") lock = obj.locks.get("puppet")
lock += f" or pid({account.id})" lock += f" or pid({account.id})"
@ -331,7 +328,7 @@ class ObjectAdmin(admin.ModelAdmin):
request, request,
"Did the following (where possible): " "Did the following (where possible): "
f"Set Account.db._last_puppet = {obj}, " f"Set Account.db._last_puppet = {obj}, "
f"Added {obj} to Account.db._playable_characters list, " f"Added {obj} to Account.characters list, "
f"Added 'puppet:pid({account.id})' lock to {obj}.", f"Added 'puppet:pid({account.id})' lock to {obj}.",
) )
else: else:

View file

@ -35,8 +35,8 @@ class EvenniaWebTest(BaseEvenniaTest):
super().setUp() super().setUp()
# Add chars to account rosters # Add chars to account rosters
self.account.db._playable_characters = [self.char1] self.account.characters.add(self.char1)
self.account2.db._playable_characters = [self.char2] self.account2.characters.add(self.char2)
for account in (self.account, self.account2): for account in (self.account, self.account2):
# Demote accounts to Player permissions # Demote accounts to Player permissions
@ -44,15 +44,15 @@ class EvenniaWebTest(BaseEvenniaTest):
account.permissions.remove("Developer") account.permissions.remove("Developer")
# Grant permissions to chars # Grant permissions to chars
for char in account.db._playable_characters: for char in account.characters:
char.locks.add("edit:id(%s) or perm(Admin)" % account.pk) char.locks.add("edit:id(%s) or perm(Admin)" % account.pk)
char.locks.add("delete:id(%s) or perm(Admin)" % account.pk) char.locks.add("delete:id(%s) or perm(Admin)" % account.pk)
char.locks.add("view:all()") char.locks.add("view:all()")
def test_valid_chars(self): def test_valid_chars(self):
"Make sure account has playable characters" "Make sure account has playable characters"
self.assertTrue(self.char1 in self.account.db._playable_characters) self.assertTrue(self.char1 in self.account.characters)
self.assertTrue(self.char2 in self.account2.db._playable_characters) self.assertTrue(self.char2 in self.account2.characters)
def get_kwargs(self): def get_kwargs(self):
return {} return {}
@ -220,7 +220,7 @@ class CharacterCreateView(EvenniaWebTest):
@override_settings(MAX_NR_CHARACTERS=1) @override_settings(MAX_NR_CHARACTERS=1)
def test_valid_access_multisession_0(self): def test_valid_access_multisession_0(self):
"Account1 with no characters should be able to create a new one" "Account1 with no characters should be able to create a new one"
self.account.db._playable_characters = [] self.account.characters.remove(self.char1)
# Login account # Login account
self.login() self.login()
@ -233,9 +233,9 @@ class CharacterCreateView(EvenniaWebTest):
# Make sure the character was actually created # Make sure the character was actually created
self.assertTrue( self.assertTrue(
len(self.account.db._playable_characters) == 1, len(self.account.characters) == 1,
"Account only has the following characters attributed to it: %s" "Account only has the following characters attributed to it: %s"
% self.account.db._playable_characters, % self.account.characters,
) )
@override_settings(MAX_NR_CHARACTERS=5) @override_settings(MAX_NR_CHARACTERS=5)
@ -252,9 +252,9 @@ class CharacterCreateView(EvenniaWebTest):
# Make sure the character was actually created # Make sure the character was actually created
self.assertTrue( self.assertTrue(
len(self.account.db._playable_characters) > 1, len(self.account.characters) > 1,
"Account only has the following characters attributed to it: %s" "Account only has the following characters attributed to it: %s"
% self.account.db._playable_characters, % self.account.characters,
) )
@ -352,7 +352,7 @@ class CharacterDeleteView(EvenniaWebTest):
# Make sure it deleted # Make sure it deleted
self.assertFalse( self.assertFalse(
self.char1 in self.account.db._playable_characters, self.char1 in self.account.characters,
"Char1 is still in Account playable characters list.", "Char1 is still in Account playable characters list.",
) )

View file

@ -102,7 +102,7 @@ def collect_topics(account):
cmd_help_topics = [] cmd_help_topics = []
if not str(account) == "AnonymousUser": if not str(account) == "AnonymousUser":
# create list of account and account's puppets # create list of account and account's puppets
puppets = account.db._playable_characters + [account] puppets = account.characters.all() + [account]
# add the account's and puppets' commands to cmd_help_topics list # add the account's and puppets' commands to cmd_help_topics list
for puppet in puppets: for puppet in puppets:
for cmdset in puppet.cmdset.get(): for cmdset in puppet.cmdset.get():

View file

@ -83,7 +83,7 @@ dependencies = [
"anything ==0.2.1", "anything ==0.2.1",
"black >= 22.6", "black >= 22.6",
"isort >= 5.10", "isort >= 5.10",
"parameterized ==0.8.1", "parameterized ==0.8.1"
] ]
[project.optional-dependencies] [project.optional-dependencies]