Format code with black. Add makefile to run fmt/tests

This commit is contained in:
Griatch 2019-09-28 18:18:11 +02:00
parent d00bce9288
commit c2c7fa311a
299 changed files with 19037 additions and 11611 deletions

View file

@ -26,11 +26,12 @@ from evennia.commands import cmdhandler
from evennia.server.models import ServerConfig
from evennia.server.throttle import Throttle
from evennia.utils import class_from_module, create, logger
from evennia.utils.utils import (lazy_property, to_str,
make_iter, is_iter,
variable_from_module)
from evennia.server.signals import (SIGNAL_ACCOUNT_POST_CREATE, SIGNAL_OBJECT_POST_PUPPET,
SIGNAL_OBJECT_POST_UNPUPPET)
from evennia.utils.utils import lazy_property, to_str, make_iter, is_iter, variable_from_module
from evennia.server.signals import (
SIGNAL_ACCOUNT_POST_CREATE,
SIGNAL_OBJECT_POST_PUPPET,
SIGNAL_OBJECT_POST_UNPUPPET,
)
from evennia.typeclasses.attributes import NickHandler
from evennia.scripts.scripthandler import ScriptHandler
from evennia.commands.cmdsethandler import CmdSetHandler
@ -43,7 +44,7 @@ __all__ = ("DefaultAccount",)
_SESSIONS = None
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
_MULTISESSION_MODE = settings.MULTISESSION_MODE
_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
_CMDSET_ACCOUNT = settings.CMDSET_ACCOUNT
@ -202,12 +203,14 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
@lazy_property
def options(self):
return OptionHandler(self,
options_dict=settings.OPTIONS_ACCOUNT_DEFAULT,
savefunc=self.attributes.add,
loadfunc=self.attributes.get,
save_kwargs={"category": 'option'},
load_kwargs={"category": 'option'})
return OptionHandler(
self,
options_dict=settings.OPTIONS_ACCOUNT_DEFAULT,
savefunc=self.attributes.add,
loadfunc=self.attributes.get,
save_kwargs={"category": "option"},
load_kwargs={"category": "option"},
)
# Do not make this a lazy property; the web UI will not refresh it!
@property
@ -266,7 +269,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# already puppeting this object
self.msg("You are already puppeting this object.")
return
if not obj.access(self, 'puppet'):
if not obj.access(self, "puppet"):
# no access
self.msg(f"You don't have permission to puppet '{obj.key}'.")
return
@ -389,6 +392,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if _MULTISESSION_MODE in (0, 1):
return puppets and puppets[0] or None
return puppets
character = property(__get_single_puppet)
puppet = property(__get_single_puppet)
@ -407,21 +411,23 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
"""
ip = kwargs.get('ip', '').strip()
username = kwargs.get('username', '').lower().strip()
ip = kwargs.get("ip", "").strip()
username = kwargs.get("username", "").lower().strip()
# Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0] == username for tup in bans if username) or
any(tup[2].match(ip) for tup in bans if ip and tup[2])):
if bans and (
any(tup[0] == username for tup in bans if username)
or any(tup[2].match(ip) for tup in bans if ip and tup[2])
):
return True
return False
@classmethod
def get_username_validators(
cls, validator_config=getattr(
settings, 'AUTH_USERNAME_VALIDATORS', [])):
cls, validator_config=getattr(settings, "AUTH_USERNAME_VALIDATORS", [])
):
"""
Retrieves and instantiates validators for usernames.
@ -436,16 +442,18 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
objs = []
for validator in validator_config:
try:
klass = import_string(validator['NAME'])
klass = import_string(validator["NAME"])
except ImportError:
msg = (f"The module in NAME could not be imported: {validator['NAME']}. "
"Check your AUTH_USERNAME_VALIDATORS setting.")
msg = (
f"The module in NAME could not be imported: {validator['NAME']}. "
"Check your AUTH_USERNAME_VALIDATORS setting."
)
raise ImproperlyConfigured(msg)
objs.append(klass(**validator.get('OPTIONS', {})))
objs.append(klass(**validator.get("OPTIONS", {})))
return objs
@classmethod
def authenticate(cls, username, password, ip='', **kwargs):
def authenticate(cls, username, password, ip="", **kwargs):
"""
Checks the given username/password against the database to see if the
credentials are valid.
@ -480,7 +488,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# See if authentication is currently being throttled
if ip and LOGIN_THROTTLE.check(ip):
errors.append('Too many login failures; please try again in a few minutes.')
errors.append("Too many login failures; please try again in a few minutes.")
# With throttle active, do not log continued hits-- it is a
# waste of storage and can be abused to make your logs harder to
@ -491,27 +499,29 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
banned = cls.is_banned(username=username, ip=ip)
if banned:
# this is a banned IP or name!
errors.append("|rYou have been banned and cannot continue from here."
"\nIf you feel this ban is in error, please email an admin.|x")
logger.log_sec(f'Authentication Denied (Banned): {username} (IP: {ip}).')
LOGIN_THROTTLE.update(ip, 'Too many sightings of banned artifact.')
errors.append(
"|rYou have been banned and cannot continue from here."
"\nIf you feel this ban is in error, please email an admin.|x"
)
logger.log_sec(f"Authentication Denied (Banned): {username} (IP: {ip}).")
LOGIN_THROTTLE.update(ip, "Too many sightings of banned artifact.")
return None, errors
# Authenticate and get Account object
account = authenticate(username=username, password=password)
if not account:
# User-facing message
errors.append('Username and/or password is incorrect.')
errors.append("Username and/or password is incorrect.")
# Log auth failures while throttle is inactive
logger.log_sec(f'Authentication Failure: {username} (IP: {ip}).')
logger.log_sec(f"Authentication Failure: {username} (IP: {ip}).")
# Update throttle
if ip:
LOGIN_THROTTLE.update(ip, 'Too many authentication failures.')
LOGIN_THROTTLE.update(ip, "Too many authentication failures.")
# Try to call post-failure hook
session = kwargs.get('session', None)
session = kwargs.get("session", None)
if session:
account = AccountDB.objects.get_account_from_name(username)
if account:
@ -520,7 +530,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
return None, errors
# Account successfully authenticated
logger.log_sec(f'Authentication Success: {account} (IP: {ip}).')
logger.log_sec(f"Authentication Success: {account} (IP: {ip}).")
return account, errors
@classmethod
@ -659,17 +669,19 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
account = None
errors = []
username = kwargs.get('username')
password = kwargs.get('password')
email = kwargs.get('email', '').strip()
guest = kwargs.get('guest', False)
username = kwargs.get("username")
password = kwargs.get("password")
email = kwargs.get("email", "").strip()
guest = kwargs.get("guest", False)
permissions = kwargs.get('permissions', settings.PERMISSION_ACCOUNT_DEFAULT)
typeclass = kwargs.get('typeclass', cls)
permissions = kwargs.get("permissions", settings.PERMISSION_ACCOUNT_DEFAULT)
typeclass = kwargs.get("typeclass", cls)
ip = kwargs.get('ip', '')
ip = kwargs.get("ip", "")
if ip and CREATION_THROTTLE.check(ip):
errors.append("You are creating too many accounts. Please log into an existing account.")
errors.append(
"You are creating too many accounts. Please log into an existing account."
)
return None, errors
# Normalize username
@ -696,19 +708,25 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
banned = cls.is_banned(username=username, ip=ip)
if banned:
# this is a banned IP or name!
string = "|rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.|x"
string = (
"|rYou have been banned and cannot continue from here."
"\nIf you feel this ban is in error, please email an admin.|x"
)
errors.append(string)
return None, errors
# everything's ok. Create the new account.
try:
try:
account = create.create_account(username, email, password, permissions=permissions, typeclass=typeclass)
logger.log_sec(f'Account Created: {account} (IP: {ip}).')
account = create.create_account(
username, email, password, permissions=permissions, typeclass=typeclass
)
logger.log_sec(f"Account Created: {account} (IP: {ip}).")
except Exception as e:
errors.append("There was an error creating the Account. If this problem persists, contact an admin.")
errors.append(
"There was an error creating the Account. If this problem persists, contact an admin."
)
logger.log_trace()
return None, errors
@ -730,14 +748,20 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if account and settings.MULTISESSION_MODE < 2:
# Load the appropriate Character class
character_typeclass = kwargs.get('character_typeclass', settings.BASE_CHARACTER_TYPECLASS)
character_home = kwargs.get('home')
character_typeclass = kwargs.get(
"character_typeclass", settings.BASE_CHARACTER_TYPECLASS
)
character_home = kwargs.get("home")
Character = class_from_module(character_typeclass)
# Create the character
character, errs = Character.create(
account.key, account, ip=ip, typeclass=character_typeclass,
permissions=permissions, home=character_home
account.key,
account,
ip=ip,
typeclass=character_typeclass,
permissions=permissions,
home=character_home,
)
errors.extend(errs)
@ -758,7 +782,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# Update the throttle to indicate a new account was created from this IP
if ip and not guest:
CREATION_THROTTLE.update(ip, 'Too many accounts being created.')
CREATION_THROTTLE.update(ip, "Too many accounts being created.")
SIGNAL_ACCOUNT_POST_CREATE.send(sender=account, ip=ip)
return account, errors
@ -786,6 +810,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
self.nicks.clear()
self.aliases.clear()
super().delete(*args, **kwargs)
# methods inherited from database model
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
@ -826,7 +851,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
kwargs["options"] = options
if text is not None:
kwargs['text'] = to_str(text)
kwargs["text"] = to_str(text)
# session relay
sessions = make_iter(session) if session else self.sessions.all()
@ -853,17 +878,29 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
commands at run-time.
"""
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_account=False)
raw_string = self.nicks.nickreplace(
raw_string, categories=("inputline", "channel"), include_account=False
)
if not session and _MULTISESSION_MODE in (0, 1):
# for these modes we use the first/only session
sessions = self.sessions.get()
session = sessions[0] if sessions else None
return cmdhandler.cmdhandler(self, raw_string,
callertype="account", session=session, **kwargs)
return cmdhandler.cmdhandler(
self, raw_string, callertype="account", session=session, **kwargs
)
def search(self, searchdata, return_puppet=False, search_object=False,
typeclass=None, nofound_string=None, multimatch_string=None, use_nicks=True, **kwargs):
def search(
self,
searchdata,
return_puppet=False,
search_object=False,
typeclass=None,
nofound_string=None,
multimatch_string=None,
use_nicks=True,
**kwargs,
):
"""
This is similar to `DefaultObject.search` but defaults to searching
for Accounts only.
@ -900,17 +937,25 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# handle me, self and *me, *self
if isinstance(searchdata, str):
# handle wrapping of common terms
if searchdata.lower() in ("me", "*me", "self", "*self",):
if searchdata.lower() in ("me", "*me", "self", "*self"):
return self
if search_object:
matches = ObjectDB.objects.object_search(searchdata, typeclass=typeclass, use_nicks=use_nicks)
matches = ObjectDB.objects.object_search(
searchdata, typeclass=typeclass, use_nicks=use_nicks
)
else:
searchdata = self.nicks.nickreplace(searchdata, categories=("account", ), include_account=False)
searchdata = self.nicks.nickreplace(
searchdata, categories=("account",), include_account=False
)
matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass)
matches = _AT_SEARCH_RESULT(matches, self, query=searchdata,
nofound_string=nofound_string,
multimatch_string=multimatch_string)
matches = _AT_SEARCH_RESULT(
matches,
self,
query=searchdata,
nofound_string=nofound_string,
multimatch_string=multimatch_string,
)
if matches and return_puppet:
try:
return matches.puppet
@ -918,7 +963,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
return None
return matches
def access(self, accessing_obj, access_type='read', default=False, no_superuser_bypass=False, **kwargs):
def access(
self, accessing_obj, access_type="read", default=False, no_superuser_bypass=False, **kwargs
):
"""
Determines if another object has permission to access this
object in whatever way.
@ -938,8 +985,12 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
result (bool): Result of access check.
"""
result = super().access(accessing_obj, access_type=access_type,
default=default, no_superuser_bypass=no_superuser_bypass)
result = super().access(
accessing_obj,
access_type=access_type,
default=default,
no_superuser_bypass=no_superuser_bypass,
)
self.at_access(result, accessing_obj, access_type, **kwargs)
return result
@ -974,9 +1025,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
"""
# A basic security setup
lockstring = "examine:perm(Admin);edit:perm(Admin);" \
"delete:perm(Admin);boot:perm(Admin);msg:all();" \
"noidletimeout:perm(Builder) or perm(noidletimeout)"
lockstring = (
"examine:perm(Admin);edit:perm(Admin);"
"delete:perm(Admin);boot:perm(Admin);msg:all();"
"noidletimeout:perm(Builder) or perm(noidletimeout)"
)
self.locks.add(lockstring)
# The ooc account cmdset
@ -991,8 +1044,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
"""
# set an (empty) attribute holding the characters this account has
lockstring = "attrread:perm(Admins);attredit:perm(Admins);" \
"attrcreate:perm(Admins);"
lockstring = "attrread:perm(Admins);attredit:perm(Admins);" "attrcreate:perm(Admins);"
self.attributes.add("_playable_characters", [], lockstring=lockstring)
self.attributes.add("_saved_protocol_flags", {}, lockstring=lockstring)
@ -1147,13 +1199,13 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
global _MUDINFO_CHANNEL
if not _MUDINFO_CHANNEL:
try:
_MUDINFO_CHANNEL = ChannelDB.objects.filter(
db_key=settings.CHANNEL_MUDINFO["key"])[0]
_MUDINFO_CHANNEL = ChannelDB.objects.filter(db_key=settings.CHANNEL_MUDINFO["key"])[
0
]
except Exception:
logger.log_trace()
now = timezone.now()
now = "%02i-%02i-%02i(%02i:%02i)" % (now.year, now.month,
now.day, now.hour, now.minute)
now = "%02i-%02i-%02i(%02i:%02i)" % (now.year, now.month, now.day, now.hour, now.minute)
if _MUDINFO_CHANNEL:
_MUDINFO_CHANNEL.tempmsg(f"[{_MUDINFO_CHANNEL.key}, {now}]: {message}")
else:
@ -1206,8 +1258,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# 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.at_look(target=self.db._playable_characters,
session=session), session=session)
self.msg(
self.at_look(target=self.db._playable_characters, session=session), session=session
)
def at_failed_login(self, session, **kwargs):
"""
@ -1355,19 +1408,27 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
result = [f"Account |g{self.key}|n (you are Out-of-Character)"]
nsess = len(sessions)
result.append(nsess == 1 and
"\n\n|wConnected session:|n" or
f"\n\n|wConnected sessions ({nsess}):|n")
result.append(
nsess == 1
and "\n\n|wConnected session:|n"
or f"\n\n|wConnected sessions ({nsess}):|n"
)
for isess, sess in enumerate(sessions):
csessid = sess.sessid
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and
str(sess.address[0]) or
str(sess.address))
result.append("\n %s %s" % (
session and
session.sessid == csessid and
"|w* %s|n" % (isess + 1) or
" %s" % (isess + 1), addr))
addr = "%s (%s)" % (
sess.protocol_key,
isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address),
)
result.append(
"\n %s %s"
% (
session
and session.sessid == csessid
and "|w* %s|n" % (isess + 1)
or " %s" % (isess + 1),
addr,
)
)
result.append("\n\n |whelp|n - more commands")
result.append("\n |wooc <Text>|n - talk on public channel")
@ -1375,19 +1436,30 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if is_su or len(characters) < charmax:
if not characters:
result.append("\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one.")
result.append(
"\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one."
)
else:
result.append("\n |w@charcreate <name> [=description]|n - create new character")
result.append("\n |w@chardelete <name>|n - delete a character (cannot be undone!)")
result.append(
"\n |w@chardelete <name>|n - delete a character (cannot be undone!)"
)
if characters:
string_s_ending = len(characters) > 1 and "s" or ""
result.append("\n |w@ic <character>|n - enter the game (|w@ooc|n to get back here)")
if is_su:
result.append(f"\n\nAvailable character{string_s_ending} ({len(characters)}/unlimited):")
result.append(
f"\n\nAvailable character{string_s_ending} ({len(characters)}/unlimited):"
)
else:
result.append("\n\nAvailable character%s%s:"
% (string_s_ending, charmax > 1 and " (%i/%i)" % (len(characters), charmax) or ""))
result.append(
"\n\nAvailable character%s%s:"
% (
string_s_ending,
charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "",
)
)
for char in characters:
csessions = char.sessions.all()
@ -1396,9 +1468,13 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# character is already puppeted
sid = sess in sessions and sessions.index(sess) + 1
if sess and sid:
result.append(f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] (played by you in session {sid})")
result.append(
f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] (played by you in session {sid})"
)
else:
result.append(f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] (played by someone else)")
result.append(
f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] (played by someone else)"
)
else:
# character is "free to puppet"
result.append(f"\n - {char.key} [{', '.join(char.permissions.all())}]")
@ -1437,11 +1513,11 @@ class DefaultGuest(DefaultAccount):
errors = []
account = None
username = None
ip = kwargs.get('ip', '').strip()
ip = kwargs.get("ip", "").strip()
# check if guests are enabled.
if not settings.GUEST_ENABLED:
errors.append('Guest accounts are not enabled on this server.')
errors.append("Guest accounts are not enabled on this server.")
return None, errors
try:
@ -1453,7 +1529,7 @@ class DefaultGuest(DefaultAccount):
if not username:
errors.append("All guest accounts are in use. Please try again later.")
if ip:
LOGIN_THROTTLE.update(ip, 'Too many requests for Guest access.')
LOGIN_THROTTLE.update(ip, "Too many requests for Guest access.")
return None, errors
else:
# build a new account with the found guest username

View file

@ -18,34 +18,34 @@ class AccountDBChangeForm(UserChangeForm):
Modify the accountdb class.
"""
class Meta(object):
model = AccountDB
fields = '__all__'
fields = "__all__"
username = forms.RegexField(
label="Username",
max_length=30,
regex=r'^[\w. @+-]+$',
widget=forms.TextInput(
attrs={'size': '30'}),
regex=r"^[\w. @+-]+$",
widget=forms.TextInput(attrs={"size": "30"}),
error_messages={
'invalid': "This value may contain only letters, spaces, numbers "
"and @/./+/-/_ characters."},
help_text="30 characters or fewer. Letters, spaces, digits and "
"@/./+/-/_ only.")
"invalid": "This value may contain only letters, spaces, numbers "
"and @/./+/-/_ characters."
},
help_text="30 characters or fewer. Letters, spaces, digits and " "@/./+/-/_ only.",
)
def clean_username(self):
"""
Clean the username and check its existence.
"""
username = self.cleaned_data['username']
username = self.cleaned_data["username"]
if username.upper() == self.instance.username.upper():
return username
elif AccountDB.objects.filter(username__iexact=username):
raise forms.ValidationError('An account with that name '
'already exists.')
return self.cleaned_data['username']
raise forms.ValidationError("An account with that name " "already exists.")
return self.cleaned_data["username"]
class AccountDBCreationForm(UserCreationForm):
@ -55,28 +55,27 @@ class AccountDBCreationForm(UserCreationForm):
class Meta(object):
model = AccountDB
fields = '__all__'
fields = "__all__"
username = forms.RegexField(
label="Username",
max_length=30,
regex=r'^[\w. @+-]+$',
widget=forms.TextInput(
attrs={'size': '30'}),
regex=r"^[\w. @+-]+$",
widget=forms.TextInput(attrs={"size": "30"}),
error_messages={
'invalid': "This value may contain only letters, spaces, numbers "
"and @/./+/-/_ characters."},
help_text="30 characters or fewer. Letters, spaces, digits and "
"@/./+/-/_ only.")
"invalid": "This value may contain only letters, spaces, numbers "
"and @/./+/-/_ characters."
},
help_text="30 characters or fewer. Letters, spaces, digits and " "@/./+/-/_ only.",
)
def clean_username(self):
"""
Cleanup username.
"""
username = self.cleaned_data['username']
username = self.cleaned_data["username"]
if AccountDB.objects.filter(username__iexact=username):
raise forms.ValidationError('An account with that name already '
'exists.')
raise forms.ValidationError("An account with that name already " "exists.")
return username
@ -85,61 +84,66 @@ class AccountForm(forms.ModelForm):
Defines how to display Accounts
"""
class Meta(object):
model = AccountDB
fields = '__all__'
fields = "__all__"
db_key = forms.RegexField(
label="Username",
initial="AccountDummy",
max_length=30,
regex=r'^[\w. @+-]+$',
regex=r"^[\w. @+-]+$",
required=False,
widget=forms.TextInput(attrs={'size': '30'}),
widget=forms.TextInput(attrs={"size": "30"}),
error_messages={
'invalid': "This value may contain only letters, spaces, numbers"
" and @/./+/-/_ characters."},
"invalid": "This value may contain only letters, spaces, numbers"
" and @/./+/-/_ characters."
},
help_text="This should be the same as the connected Account's key "
"name. 30 characters or fewer. Letters, spaces, digits and "
"@/./+/-/_ only.")
"name. 30 characters or fewer. Letters, spaces, digits and "
"@/./+/-/_ only.",
)
db_typeclass_path = forms.CharField(
label="Typeclass",
initial=settings.BASE_ACCOUNT_TYPECLASS,
widget=forms.TextInput(
attrs={'size': '78'}),
widget=forms.TextInput(attrs={"size": "78"}),
help_text="Required. Defines what 'type' of entity this is. This "
"variable holds a Python path to a module with a valid "
"Evennia Typeclass. Defaults to "
"settings.BASE_ACCOUNT_TYPECLASS.")
"variable holds a Python path to a module with a valid "
"Evennia Typeclass. Defaults to "
"settings.BASE_ACCOUNT_TYPECLASS.",
)
db_permissions = forms.CharField(
label="Permissions",
initial=settings.PERMISSION_ACCOUNT_DEFAULT,
required=False,
widget=forms.TextInput(
attrs={'size': '78'}),
widget=forms.TextInput(attrs={"size": "78"}),
help_text="In-game permissions. A comma-separated list of text "
"strings checked by certain locks. They are often used for "
"hierarchies, such as letting an Account have permission "
"'Admin', 'Builder' etc. An Account permission can be "
"overloaded by the permissions of a controlled Character. "
"Normal accounts use 'Accounts' by default.")
"strings checked by certain locks. They are often used for "
"hierarchies, such as letting an Account have permission "
"'Admin', 'Builder' etc. An Account permission can be "
"overloaded by the permissions of a controlled Character. "
"Normal accounts use 'Accounts' by default.",
)
db_lock_storage = forms.CharField(
label="Locks",
widget=forms.Textarea(attrs={'cols': '100', 'rows': '2'}),
widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}),
required=False,
help_text="In-game lock definition string. If not given, defaults "
"will be used. This string should be on the form "
"<i>type:lockfunction(args);type2:lockfunction2(args);...")
"will be used. This string should be on the form "
"<i>type:lockfunction(args);type2:lockfunction2(args);...",
)
db_cmdset_storage = forms.CharField(
label="cmdset",
initial=settings.CMDSET_ACCOUNT,
widget=forms.TextInput(attrs={'size': '78'}),
widget=forms.TextInput(attrs={"size": "78"}),
required=False,
help_text="python path to account cmdset class (set in "
"settings.CMDSET_ACCOUNT by default)")
"settings.CMDSET_ACCOUNT by default)",
)
class AccountInline(admin.StackedInline):
@ -147,19 +151,29 @@ class AccountInline(admin.StackedInline):
Inline creation of Account
"""
model = AccountDB
template = "admin/accounts/stacked.html"
form = AccountForm
fieldsets = (
("In-game Permissions and Locks",
{'fields': ('db_lock_storage',),
#{'fields': ('db_permissions', 'db_lock_storage'),
'description': "<i>These are permissions/locks for in-game use. "
"They are unrelated to website access rights.</i>"}),
("In-game Account data",
{'fields': ('db_typeclass_path', 'db_cmdset_storage'),
'description': "<i>These fields define in-game-specific properties "
"for the Account object in-game.</i>"}))
(
"In-game Permissions and Locks",
{
"fields": ("db_lock_storage",),
# {'fields': ('db_permissions', 'db_lock_storage'),
"description": "<i>These are permissions/locks for in-game use. "
"They are unrelated to website access rights.</i>",
},
),
(
"In-game Account data",
{
"fields": ("db_typeclass_path", "db_cmdset_storage"),
"description": "<i>These fields define in-game-specific properties "
"for the Account object in-game.</i>",
},
),
)
extra = 1
max_num = 1
@ -170,6 +184,7 @@ class AccountTagInline(TagInline):
Inline Account Tags.
"""
model = AccountDB.db_tags.through
related_field = "accountdb"
@ -179,6 +194,7 @@ class AccountAttributeInline(AttributeInline):
Inline Account Attributes.
"""
model = AccountDB.db_attributes.through
related_field = "accountdb"
@ -189,30 +205,43 @@ class AccountDBAdmin(BaseUserAdmin):
"""
list_display = ('username', 'email', 'is_staff', 'is_superuser')
list_display = ("username", "email", "is_staff", "is_superuser")
form = AccountDBChangeForm
add_form = AccountDBCreationForm
inlines = [AccountTagInline, AccountAttributeInline]
fieldsets = (
(None, {'fields': ('username', 'password', 'email')}),
('Website profile', {
'fields': ('first_name', 'last_name'),
'description': "<i>These are not used "
"in the default system.</i>"}),
('Website dates', {
'fields': ('last_login', 'date_joined'),
'description': '<i>Relevant only to the website.</i>'}),
('Website Permissions', {
'fields': ('is_active', 'is_staff', 'is_superuser',
'user_permissions', 'groups'),
'description': "<i>These are permissions/permission groups for "
"accessing the admin site. They are unrelated to "
"in-game access rights.</i>"}),
('Game Options', {
'fields': ('db_typeclass_path', 'db_cmdset_storage',
'db_lock_storage'),
'description': '<i>These are attributes that are more relevant '
'to gameplay.</i>'}))
(None, {"fields": ("username", "password", "email")}),
(
"Website profile",
{
"fields": ("first_name", "last_name"),
"description": "<i>These are not used " "in the default system.</i>",
},
),
(
"Website dates",
{
"fields": ("last_login", "date_joined"),
"description": "<i>Relevant only to the website.</i>",
},
),
(
"Website Permissions",
{
"fields": ("is_active", "is_staff", "is_superuser", "user_permissions", "groups"),
"description": "<i>These are permissions/permission groups for "
"accessing the admin site. They are unrelated to "
"in-game access rights.</i>",
},
),
(
"Game Options",
{
"fields": ("db_typeclass_path", "db_cmdset_storage", "db_lock_storage"),
"description": "<i>These are attributes that are more relevant " "to gameplay.</i>",
},
),
)
# ('Game Options', {'fields': (
# 'db_typeclass_path', 'db_cmdset_storage',
# 'db_permissions', 'db_lock_storage'),
@ -220,10 +249,15 @@ class AccountDBAdmin(BaseUserAdmin):
# 'more relevant to gameplay.</i>'}))
add_fieldsets = (
(None,
{'fields': ('username', 'password1', 'password2', 'email'),
'description': "<i>These account details are shared by the admin "
"system and the game.</i>"},),)
(
None,
{
"fields": ("username", "password1", "password2", "email"),
"description": "<i>These account details are shared by the admin "
"system and the game.</i>",
},
),
)
def save_model(self, request, obj, form, change):
"""
@ -246,6 +280,7 @@ class AccountDBAdmin(BaseUserAdmin):
def response_add(self, request, obj, post_url_continue=None):
from django.http import HttpResponseRedirect
from django.urls import reverse
return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id]))

View file

@ -23,6 +23,7 @@ _SESSIONS = None
# Bot helper utilities
class BotStarter(DefaultScript):
"""
This non-repeating script has the
@ -80,6 +81,7 @@ class BotStarter(DefaultScript):
"""
self.db.started = False
#
# Bot base class
@ -100,8 +102,10 @@ class Bot(DefaultAccount):
# the text encoding to use.
self.db.encoding = "utf-8"
# A basic security setup (also avoid idle disconnects)
lockstring = "examine:perm(Admin);edit:perm(Admin);delete:perm(Admin);" \
"boot:perm(Admin);msg:false();noidletimeout:true()"
lockstring = (
"examine:perm(Admin);edit:perm(Admin);delete:perm(Admin);"
"boot:perm(Admin);msg:false();noidletimeout:true()"
)
self.locks.add(lockstring)
# set the basics of being a bot
script_key = str(self.key)
@ -143,16 +147,25 @@ class Bot(DefaultAccount):
# IRC
class IRCBot(Bot):
"""
Bot for handling IRC connections.
"""
# override this on a child class to use custom factory
factory_path = "evennia.server.portal.irc.IRCBotFactory"
def start(self, ev_channel=None, irc_botname=None, irc_channel=None,
irc_network=None, irc_port=None, irc_ssl=None):
def start(
self,
ev_channel=None,
irc_botname=None,
irc_channel=None,
irc_network=None,
irc_port=None,
irc_ssl=None,
):
"""
Start by telling the portal to start a new session.
@ -202,12 +215,14 @@ class IRCBot(Bot):
# instruct the server and portal to create a new session with
# the stored configuration
configdict = {"uid": self.dbid,
"botname": self.db.irc_botname,
"channel": self.db.irc_channel,
"network": self.db.irc_network,
"port": self.db.irc_port,
"ssl": self.db.irc_ssl}
configdict = {
"uid": self.dbid,
"botname": self.db.irc_botname,
"channel": self.db.irc_channel,
"network": self.db.irc_network,
"port": self.db.irc_port,
"ssl": self.db.irc_ssl,
}
_SESSIONS.start_bot_session(self.factory_path, configdict)
def at_msg_send(self, **kwargs):
@ -275,8 +290,11 @@ class IRCBot(Bot):
# cache channel lookup
self.ndb.ev_channel = self.db.ev_channel
if ("from_channel" in options and text and
self.ndb.ev_channel.dbid == options["from_channel"]):
if (
"from_channel" in options
and text
and self.ndb.ev_channel.dbid == options["from_channel"]
):
if not from_obj or from_obj != [self]:
super().msg(channel=text)
@ -338,9 +356,14 @@ class IRCBot(Bot):
delta_cmd = t0 - sess.cmd_last_visible
delta_conn = t0 - session.conn_time
account = sess.get_account()
whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % account.name, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1)))
whos.append(
"%s (%s/%s)"
% (
utils.crop("|w%s|n" % account.name, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1),
)
)
text = f"Who list (online/idle): {', '.join(sorted(whos, key=lambda w: w.lower()))}"
elif txt.lower().startswith("about"):
# some bot info
@ -364,6 +387,7 @@ class IRCBot(Bot):
if self.ndb.ev_channel:
self.ndb.ev_channel.msg(text, senders=self)
#
# RSS
@ -410,9 +434,7 @@ class RSSBot(Bot):
self.db.rss_rate = rss_rate
# instruct the server and portal to create a new session with
# the stored configuration
configdict = {"uid": self.dbid,
"url": self.db.rss_url,
"rate": self.db.rss_rate}
configdict = {"uid": self.dbid, "url": self.db.rss_url, "rate": self.db.rss_rate}
_SESSIONS.start_bot_session("evennia.server.portal.rss.RSSBotFactory", configdict)
def execute_cmd(self, txt=None, session=None, **kwargs):
@ -437,12 +459,14 @@ class RSSBot(Bot):
# Grapevine bot
class GrapevineBot(Bot):
"""
g Grapevine (https://grapevine.haus) relayer. The channel to connect to is the first
name in the settings.GRAPEVINE_CHANNELS list.
"""
factory_path = "evennia.server.portal.grapevine.RestartingWebsocketServerFactory"
def start(self, ev_channel=None, grapevine_channel=None):
@ -472,8 +496,7 @@ class GrapevineBot(Bot):
self.db.grapevine_channel = grapevine_channel
# these will be made available as properties on the protocol factory
configdict = {"uid": self.dbid,
"grapevine_channel": self.db.grapevine_channel}
configdict = {"uid": self.dbid, "grapevine_channel": self.db.grapevine_channel}
_SESSIONS.start_bot_session(self.factory_path, configdict)
@ -501,8 +524,11 @@ class GrapevineBot(Bot):
# cache channel lookup
self.ndb.ev_channel = self.db.ev_channel
if ("from_channel" in options and text and
self.ndb.ev_channel.dbid == options["from_channel"]):
if (
"from_channel" in options
and text
and self.ndb.ev_channel.dbid == options["from_channel"]
):
if not from_obj or from_obj != [self]:
# send outputfunc channel(msg, chan, sender)
@ -511,11 +537,25 @@ class GrapevineBot(Bot):
# prefix since we pass that explicitly anyway
prefix, text = text.split(":", 1)
super().msg(channel=(text.strip(), self.db.grapevine_channel,
", ".join(obj.key for obj in from_obj), {}))
super().msg(
channel=(
text.strip(),
self.db.grapevine_channel,
", ".join(obj.key for obj in from_obj),
{},
)
)
def execute_cmd(self, txt=None, session=None, event=None, grapevine_channel=None,
sender=None, game=None, **kwargs):
def execute_cmd(
self,
txt=None,
session=None,
event=None,
grapevine_channel=None,
sender=None,
game=None,
**kwargs,
):
"""
Take incoming data from protocol and send it to connected channel. This is
triggered by the bot_data_in Inputfunc.

View file

@ -5,7 +5,8 @@ The managers for the custom Account object and permissions.
import datetime
from django.utils import timezone
from django.contrib.auth.models import UserManager
from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager)
from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
__all__ = ("AccountManager",)
@ -13,6 +14,7 @@ __all__ = ("AccountManager",)
# Account Manager
#
class AccountDBManager(TypedObjectManager, UserManager):
"""
This AccountManager implements methods for searching
@ -90,8 +92,7 @@ class AccountDBManager(TypedObjectManager, UserManager):
end_date = timezone.now()
tdelta = datetime.timedelta(days)
start_date = end_date - tdelta
return self.filter(last_login__range=(
start_date, end_date)).order_by('-last_login')
return self.filter(last_login__range=(start_date, end_date)).order_by("-last_login")
def get_account_from_email(self, uemail):
"""
@ -176,7 +177,8 @@ class AccountDBManager(TypedObjectManager, UserManager):
# try alias match
matches = self.filter(
db_tags__db_tagtype__iexact="alias",
**{"db_tags__db_key__iexact" if exact else "db_tags__db_key__icontains": ostring})
**{"db_tags__db_key__iexact" if exact else "db_tags__db_key__icontains": ostring},
)
return matches
# back-compatibility alias

View file

@ -8,42 +8,168 @@ import django.core.validators
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
('typeclasses', '0001_initial'),
]
dependencies = [("auth", "0001_initial"), ("typeclasses", "0001_initial")]
operations = [
migrations.CreateModel(
name='AccountDB',
name="AccountDB",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('db_key', models.CharField(max_length=255, verbose_name='key', db_index=True)),
('db_typeclass_path', models.CharField(help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name='typeclass')),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
('db_lock_storage', models.TextField(help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name='locks', blank=True)),
('db_is_connected', models.BooleanField(default=False, help_text='If account is connected to game or not', verbose_name='is_connected')),
('db_cmdset_storage', models.CharField(help_text='optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.', max_length=255, null=True, verbose_name='cmdset')),
('db_is_bot', models.BooleanField(default=False, help_text='Used to identify irc/rss bots', verbose_name='is_bot')),
('db_attributes', models.ManyToManyField(help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
('db_tags', models.ManyToManyField(help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True)),
('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')),
('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
(
"id",
models.AutoField(
verbose_name="ID", serialize=False, auto_created=True, primary_key=True
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.",
unique=True,
max_length=30,
verbose_name="username",
validators=[
django.core.validators.RegexValidator(
"^[\\w.@+-]+$", "Enter a valid username.", "invalid"
)
],
),
),
(
"first_name",
models.CharField(max_length=30, verbose_name="first name", blank=True),
),
(
"last_name",
models.CharField(max_length=30, verbose_name="last name", blank=True),
),
(
"email",
models.EmailField(max_length=75, verbose_name="email address", blank=True),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
("db_key", models.CharField(max_length=255, verbose_name="key", db_index=True)),
(
"db_typeclass_path",
models.CharField(
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.",
max_length=255,
null=True,
verbose_name="typeclass",
),
),
(
"db_date_created",
models.DateTimeField(auto_now_add=True, verbose_name="creation date"),
),
(
"db_lock_storage",
models.TextField(
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.",
verbose_name="locks",
blank=True,
),
),
(
"db_is_connected",
models.BooleanField(
default=False,
help_text="If account is connected to game or not",
verbose_name="is_connected",
),
),
(
"db_cmdset_storage",
models.CharField(
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.",
max_length=255,
null=True,
verbose_name="cmdset",
),
),
(
"db_is_bot",
models.BooleanField(
default=False,
help_text="Used to identify irc/rss bots",
verbose_name="is_bot",
),
),
(
"db_attributes",
models.ManyToManyField(
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
to="typeclasses.Attribute",
null=True,
),
),
(
"db_tags",
models.ManyToManyField(
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
to="typeclasses.Tag",
null=True,
),
),
(
"groups",
models.ManyToManyField(
related_query_name="user",
related_name="user_set",
to="auth.Group",
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of his/her group.",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
related_query_name="user",
related_name="user_set",
to="auth.Permission",
blank=True,
help_text="Specific permissions for this user.",
verbose_name="user permissions",
),
),
],
options={
'verbose_name': 'Account',
'verbose_name_plural': 'Accounts',
},
options={"verbose_name": "Account", "verbose_name_plural": "Accounts"},
bases=(models.Model,),
),
)
]

View file

@ -13,10 +13,6 @@ def convert_defaults(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
dependencies = [("accounts", "0001_initial")]
operations = [
migrations.RunPython(convert_defaults),
]
operations = [migrations.RunPython(convert_defaults)]

View file

@ -6,31 +6,17 @@ from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_move_defaults'),
]
dependencies = [("accounts", "0002_move_defaults")]
operations = [
migrations.CreateModel(
name='DefaultAccount',
fields=[
],
options={
'proxy': True,
},
bases=('accounts.accountdb',),
name="DefaultAccount", fields=[], options={"proxy": True}, bases=("accounts.accountdb",)
),
migrations.CreateModel(
name='DefaultGuest',
fields=[
],
options={
'proxy': True,
},
bases=('accounts.defaultaccount',),
),
migrations.AlterModelOptions(
name='accountdb',
options={'verbose_name': 'Account'},
name="DefaultGuest",
fields=[],
options={"proxy": True},
bases=("accounts.defaultaccount",),
),
migrations.AlterModelOptions(name="accountdb", options={"verbose_name": "Account"}),
]

View file

@ -8,41 +8,52 @@ import django.core.validators
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_auto_20150209_2234'),
]
dependencies = [("accounts", "0003_auto_20150209_2234")]
operations = [
migrations.DeleteModel(
name='DefaultGuest',
),
migrations.DeleteModel(
name='DefaultAccount',
),
migrations.DeleteModel(name="DefaultGuest"),
migrations.DeleteModel(name="DefaultAccount"),
migrations.AlterModelManagers(
name='accountdb',
managers=[
('objects', evennia.accounts.manager.AccountDBManager()),
],
name="accountdb", managers=[("objects", evennia.accounts.manager.AccountDBManager())]
),
migrations.AlterField(
model_name='accountdb',
name='email',
field=models.EmailField(max_length=254, verbose_name='email address', blank=True),
model_name="accountdb",
name="email",
field=models.EmailField(max_length=254, verbose_name="email address", blank=True),
),
migrations.AlterField(
model_name='accountdb',
name='groups',
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'),
model_name="accountdb",
name="groups",
field=models.ManyToManyField(
related_query_name="user",
related_name="user_set",
to="auth.Group",
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
verbose_name="groups",
),
),
migrations.AlterField(
model_name='accountdb',
name='last_login',
field=models.DateTimeField(null=True, verbose_name='last login', blank=True),
model_name="accountdb",
name="last_login",
field=models.DateTimeField(null=True, verbose_name="last login", blank=True),
),
migrations.AlterField(
model_name='accountdb',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'),
model_name="accountdb",
name="username",
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
max_length=30,
validators=[
django.core.validators.RegexValidator(
"^[\\w.@+-]+$",
"Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.",
"invalid",
)
],
help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.",
unique=True,
verbose_name="username",
),
),
]

View file

@ -8,14 +8,24 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_auto_20150403_2339'),
]
dependencies = [("accounts", "0004_auto_20150403_2339")]
operations = [
migrations.AlterField(
model_name='accountdb',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
),
model_name="accountdb",
name="username",
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=30,
unique=True,
validators=[
django.core.validators.RegexValidator(
"^[\\w.@+-]+$",
"Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.",
)
],
verbose_name="username",
),
)
]

View file

@ -8,24 +8,35 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0005_auto_20160905_0902'),
]
dependencies = [("accounts", "0005_auto_20160905_0902")]
operations = [
migrations.AlterField(
model_name='accountdb',
name='db_attributes',
field=models.ManyToManyField(help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'),
model_name="accountdb",
name="db_attributes",
field=models.ManyToManyField(
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
to="typeclasses.Attribute",
),
),
migrations.AlterField(
model_name='accountdb',
name='db_tags',
field=models.ManyToManyField(help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'),
model_name="accountdb",
name="db_tags",
field=models.ManyToManyField(
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
to="typeclasses.Tag",
),
),
migrations.AlterField(
model_name='accountdb',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'),
model_name="accountdb",
name="username",
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.ASCIIUsernameValidator()],
verbose_name="username",
),
),
]

View file

@ -8,31 +8,33 @@ from django.db import migrations
def forwards(apps, schema_editor):
try:
PlayerDB = apps.get_model('players', 'PlayerDB')
PlayerDB = apps.get_model("players", "PlayerDB")
except LookupError:
# playerdb not available. Skip.
return
AccountDB = apps.get_model('accounts', 'AccountDB')
AccountDB = apps.get_model("accounts", "AccountDB")
for player in PlayerDB.objects.all():
account = AccountDB(id=player.id,
password=player.password,
is_superuser=player.is_superuser,
last_login=player.last_login,
username=player.username,
first_name=player.first_name,
last_name=player.last_name,
email=player.email,
is_staff=player.is_staff,
is_active=player.is_active,
date_joined=player.date_joined,
db_key=player.db_key,
db_typeclass_path=player.db_typeclass_path,
db_date_created=player.db_date_created,
db_lock_storage=player.db_lock_storage,
db_is_connected=player.db_is_connected,
db_cmdset_storage=player.db_cmdset_storage,
db_is_bot=player.db_is_bot)
account = AccountDB(
id=player.id,
password=player.password,
is_superuser=player.is_superuser,
last_login=player.last_login,
username=player.username,
first_name=player.first_name,
last_name=player.last_name,
email=player.email,
is_staff=player.is_staff,
is_active=player.is_active,
date_joined=player.date_joined,
db_key=player.db_key,
db_typeclass_path=player.db_typeclass_path,
db_date_created=player.db_date_created,
db_lock_storage=player.db_lock_storage,
db_is_connected=player.db_is_connected,
db_cmdset_storage=player.db_cmdset_storage,
db_is_bot=player.db_is_bot,
)
account.save()
for group in player.groups.all():
account.groups.add(group)
@ -46,13 +48,9 @@ def forwards(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
('accounts', '0006_auto_20170606_1731'),
]
dependencies = [("accounts", "0006_auto_20170606_1731")]
operations = [
migrations.RunPython(forwards, migrations.RunPython.noop)
]
operations = [migrations.RunPython(forwards, migrations.RunPython.noop)]
if global_apps.is_installed('players'):
dependencies.append(('players', '0006_auto_20170606_1731'))
if global_apps.is_installed("players"):
dependencies.append(("players", "0006_auto_20170606_1731"))

View file

@ -8,65 +8,93 @@ import evennia.accounts.manager
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_copy_player_to_account'),
]
dependencies = [("accounts", "0007_copy_player_to_account")]
operations = [
migrations.AlterModelManagers(
name='accountdb',
managers=[
('objects', evennia.accounts.manager.AccountDBManager()),
],
name="accountdb", managers=[("objects", evennia.accounts.manager.AccountDBManager())]
),
migrations.AlterField(
model_name='accountdb',
name='db_attributes',
field=models.ManyToManyField(help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'),
model_name="accountdb",
name="db_attributes",
field=models.ManyToManyField(
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
to="typeclasses.Attribute",
),
),
migrations.AlterField(
model_name='accountdb',
name='db_cmdset_storage',
field=models.CharField(help_text='optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.', max_length=255, null=True, verbose_name='cmdset'),
model_name="accountdb",
name="db_cmdset_storage",
field=models.CharField(
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.",
max_length=255,
null=True,
verbose_name="cmdset",
),
),
migrations.AlterField(
model_name='accountdb',
name='db_date_created',
field=models.DateTimeField(auto_now_add=True, verbose_name='creation date'),
model_name="accountdb",
name="db_date_created",
field=models.DateTimeField(auto_now_add=True, verbose_name="creation date"),
),
migrations.AlterField(
model_name='accountdb',
name='db_is_bot',
field=models.BooleanField(default=False, help_text='Used to identify irc/rss bots', verbose_name='is_bot'),
model_name="accountdb",
name="db_is_bot",
field=models.BooleanField(
default=False, help_text="Used to identify irc/rss bots", verbose_name="is_bot"
),
),
migrations.AlterField(
model_name='accountdb',
name='db_is_connected',
field=models.BooleanField(default=False, help_text='If player is connected to game or not', verbose_name='is_connected'),
model_name="accountdb",
name="db_is_connected",
field=models.BooleanField(
default=False,
help_text="If player is connected to game or not",
verbose_name="is_connected",
),
),
migrations.AlterField(
model_name='accountdb',
name='db_key',
field=models.CharField(db_index=True, max_length=255, verbose_name='key'),
model_name="accountdb",
name="db_key",
field=models.CharField(db_index=True, max_length=255, verbose_name="key"),
),
migrations.AlterField(
model_name='accountdb',
name='db_lock_storage',
field=models.TextField(blank=True, help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name='locks'),
model_name="accountdb",
name="db_lock_storage",
field=models.TextField(
blank=True,
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.",
verbose_name="locks",
),
),
migrations.AlterField(
model_name='accountdb',
name='db_tags',
field=models.ManyToManyField(help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'),
model_name="accountdb",
name="db_tags",
field=models.ManyToManyField(
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
to="typeclasses.Tag",
),
),
migrations.AlterField(
model_name='accountdb',
name='db_typeclass_path',
field=models.CharField(help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name='typeclass'),
model_name="accountdb",
name="db_typeclass_path",
field=models.CharField(
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.",
max_length=255,
null=True,
verbose_name="typeclass",
),
),
migrations.AlterField(
model_name='accountdb',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
model_name="accountdb",
name="username",
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name="username",
),
),
]

View file

@ -27,8 +27,8 @@ from evennia.server.signals import SIGNAL_ACCOUNT_POST_RENAME
__all__ = ("AccountDB",)
#_ME = _("me")
#_SELF = _("self")
# _ME = _("me")
# _SELF = _("self")
_MULTISESSION_MODE = settings.MULTISESSION_MODE
@ -39,11 +39,12 @@ _DA = object.__delattr__
_TYPECLASS = None
#------------------------------------------------------------
# ------------------------------------------------------------
#
# AccountDB
#
#------------------------------------------------------------
# ------------------------------------------------------------
class AccountDB(TypedObject, AbstractUser):
"""
@ -83,14 +84,22 @@ class AccountDB(TypedObject, AbstractUser):
# store a connected flag here too, not just in sessionhandler.
# This makes it easier to track from various out-of-process locations
db_is_connected = models.BooleanField(default=False,
verbose_name="is_connected",
help_text="If player is connected to game or not")
db_is_connected = models.BooleanField(
default=False,
verbose_name="is_connected",
help_text="If player is connected to game or not",
)
# database storage of persistant cmdsets.
db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True,
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.")
db_cmdset_storage = models.CharField(
"cmdset",
max_length=255,
null=True,
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.",
)
# marks if this is a "virtual" bot account object
db_is_bot = models.BooleanField(default=False, verbose_name="is_bot", help_text="Used to identify irc/rss bots")
db_is_bot = models.BooleanField(
default=False, verbose_name="is_bot", help_text="Used to identify irc/rss bots"
)
# Database manager
objects = AccountDBManager()
@ -101,20 +110,20 @@ class AccountDB(TypedObject, AbstractUser):
__applabel__ = "accounts"
class Meta(object):
verbose_name = 'Account'
verbose_name = "Account"
# cmdset_storage property
# This seems very sensitive to caching, so leaving it be for now /Griatch
#@property
# @property
def __cmdset_storage_get(self):
"""
Getter. Allows for value = self.name. Returns a list of cmdset_storage.
"""
storage = self.db_cmdset_storage
# we need to check so storage is not None
return [path.strip() for path in storage.split(',')] if storage else []
return [path.strip() for path in storage.split(",")] if storage else []
#@cmdset_storage.setter
# @cmdset_storage.setter
def __cmdset_storage_set(self, value):
"""
Setter. Allows for self.name = value. Stores as a comma-separated
@ -123,11 +132,12 @@ class AccountDB(TypedObject, AbstractUser):
_SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value)))
_GA(self, "save")()
#@cmdset_storage.deleter
# @cmdset_storage.deleter
def __cmdset_storage_del(self):
"Deleter. Allows for del self.name"
_SA(self, "db_cmdset_storage", None)
_GA(self, "save")()
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del)
#
@ -140,7 +150,7 @@ class AccountDB(TypedObject, AbstractUser):
def __repr__(self):
return f"{self.name}(account#{self.dbid})"
#@property
# @property
def __username_get(self):
return self.username
@ -157,7 +167,7 @@ class AccountDB(TypedObject, AbstractUser):
name = property(__username_get, __username_set, __username_del)
key = property(__username_get, __username_set, __username_del)
#@property
# @property
def __uid_get(self):
"Getter. Retrieves the user id"
return self.id
@ -167,4 +177,5 @@ class AccountDB(TypedObject, AbstractUser):
def __uid_del(self):
raise Exception("User id cannot be deleted!")
uid = property(__uid_get, __uid_set, __uid_del)

View file

@ -20,12 +20,15 @@ class TestAccountSessionHandler(TestCase):
def setUp(self):
self.account = create.create_account(
f"TestAccount{randint(0, 999999)}", email="test@test.com",
password="testpassword", typeclass=DefaultAccount)
f"TestAccount{randint(0, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.handler = AccountSessionHandler(self.account)
def tearDown(self):
if hasattr(self, 'account'):
if hasattr(self, "account"):
self.account.delete()
def test_get(self):
@ -67,22 +70,24 @@ class TestAccountSessionHandler(TestCase):
class TestDefaultGuest(EvenniaTest):
"Check DefaultGuest class"
ip = '212.216.134.22'
ip = "212.216.134.22"
@override_settings(GUEST_ENABLED=False)
def test_create_not_enabled(self):
# Guest account should not be permitted
account, errors = DefaultGuest.authenticate(ip=self.ip)
self.assertFalse(account, 'Guest account was created despite being disabled.')
self.assertFalse(account, "Guest account was created despite being disabled.")
def test_authenticate(self):
# Create a guest account
account, errors = DefaultGuest.authenticate(ip=self.ip)
self.assertTrue(account, 'Guest account should have been created.')
self.assertTrue(account, "Guest account should have been created.")
# Create a second guest account
account, errors = DefaultGuest.authenticate(ip=self.ip)
self.assertFalse(account, 'Two guest accounts were created with a single entry on the guest list!')
self.assertFalse(
account, "Two guest accounts were created with a single entry on the guest list!"
)
@patch("evennia.accounts.accounts.ChannelDB.objects.get_channel")
def test_create(self, get_channel):
@ -112,42 +117,52 @@ class TestDefaultGuest(EvenniaTest):
class TestDefaultAccountAuth(EvenniaTest):
def setUp(self):
super(TestDefaultAccountAuth, self).setUp()
self.password = "testpassword"
self.account.delete()
self.account = create.create_account(f"TestAccount{randint(100000, 999999)}", email="test@test.com", password=self.password, typeclass=DefaultAccount)
self.account = create.create_account(
f"TestAccount{randint(100000, 999999)}",
email="test@test.com",
password=self.password,
typeclass=DefaultAccount,
)
def test_authentication(self):
"Confirm Account authentication method is authenticating/denying users."
# Valid credentials
obj, errors = DefaultAccount.authenticate(self.account.name, self.password)
self.assertTrue(obj, 'Account did not authenticate given valid credentials.')
self.assertTrue(obj, "Account did not authenticate given valid credentials.")
# Invalid credentials
obj, errors = DefaultAccount.authenticate(self.account.name, 'xyzzy')
self.assertFalse(obj, 'Account authenticated using invalid credentials.')
obj, errors = DefaultAccount.authenticate(self.account.name, "xyzzy")
self.assertFalse(obj, "Account authenticated using invalid credentials.")
def test_create(self):
"Confirm Account creation is working as expected."
# Create a normal account
account, errors = DefaultAccount.create(username='ziggy', password='stardust11')
self.assertTrue(account, 'New account should have been created.')
account, errors = DefaultAccount.create(username="ziggy", password="stardust11")
self.assertTrue(account, "New account should have been created.")
# Try creating a duplicate account
account2, errors = DefaultAccount.create(username='Ziggy', password='starman11')
self.assertFalse(account2, 'Duplicate account name should not have been allowed.')
account2, errors = DefaultAccount.create(username="Ziggy", password="starman11")
self.assertFalse(account2, "Duplicate account name should not have been allowed.")
account.delete()
def test_throttle(self):
"Confirm throttle activates on too many failures."
for x in range(20):
obj, errors = DefaultAccount.authenticate(self.account.name, 'xyzzy', ip='12.24.36.48')
self.assertFalse(obj, 'Authentication was provided a bogus password; this should NOT have returned an account!')
obj, errors = DefaultAccount.authenticate(self.account.name, "xyzzy", ip="12.24.36.48")
self.assertFalse(
obj,
"Authentication was provided a bogus password; this should NOT have returned an account!",
)
self.assertTrue('too many login failures' in errors[-1].lower(), 'Failed logins should have been throttled.')
self.assertTrue(
"too many login failures" in errors[-1].lower(),
"Failed logins should have been throttled.",
)
def test_username_validation(self):
"Check username validators deny relevant usernames"
@ -156,7 +171,7 @@ class TestDefaultAccountAuth(EvenniaTest):
if not uses_database("mysql"):
# TODO As of Mar 2019, mysql does not pass this test due to collation problems
# that has not been possible to resolve
result, error = DefaultAccount.validate_username('¯\_(ツ)_/¯')
result, error = DefaultAccount.validate_username("¯\_(ツ)_/¯")
self.assertFalse(result, "Validator allowed kanji in username.")
# Should not allow duplicate username
@ -164,35 +179,44 @@ class TestDefaultAccountAuth(EvenniaTest):
self.assertFalse(result, "Duplicate username should not have passed validation.")
# Should not allow username too short
result, error = DefaultAccount.validate_username('xx')
result, error = DefaultAccount.validate_username("xx")
self.assertFalse(result, "2-character username passed validation.")
def test_password_validation(self):
"Check password validators deny bad passwords"
account = create.create_account(f"TestAccount{randint(100000, 999999)}",
email="test@test.com", password="testpassword", typeclass=DefaultAccount)
for bad in ('', '123', 'password', 'TestAccount', '#', 'xyzzy'):
account = create.create_account(
f"TestAccount{randint(100000, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
for bad in ("", "123", "password", "TestAccount", "#", "xyzzy"):
self.assertFalse(account.validate_password(bad, account=self.account)[0])
"Check validators allow sufficiently complex passwords"
for better in ('Mxyzptlk', "j0hn, i'M 0n1y d4nc1nG"):
for better in ("Mxyzptlk", "j0hn, i'M 0n1y d4nc1nG"):
self.assertTrue(account.validate_password(better, account=self.account)[0])
account.delete()
def test_password_change(self):
"Check password setting and validation is working as expected"
account = create.create_account(f"TestAccount{randint(100000, 999999)}",
email="test@test.com", password="testpassword", typeclass=DefaultAccount)
account = create.create_account(
f"TestAccount{randint(100000, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
from django.core.exceptions import ValidationError
# Try setting some bad passwords
for bad in ('', '#', 'TestAccount', 'password'):
for bad in ("", "#", "TestAccount", "password"):
valid, error = account.validate_password(bad, account)
self.assertFalse(valid)
# Try setting a better password (test for False; returns None on success)
self.assertFalse(account.set_password('Mxyzptlk'))
self.assertFalse(account.set_password("Mxyzptlk"))
account.delete()
@ -228,8 +252,11 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler
account = create.create_account(
f"TestAccount{randint(0, 999999)}", email="test@test.com",
password="testpassword", typeclass=DefaultAccount)
f"TestAccount{randint(0, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
@ -239,7 +266,9 @@ class TestDefaultAccount(TestCase):
obj = Mock()
self.s1.puppet = obj
account.puppet_object(self.s1, obj)
self.s1.data_out.assert_called_with(options=None, text="You are already puppeting this object.")
self.s1.data_out.assert_called_with(
options=None, text="You are already puppeting this object."
)
self.assertIsNone(obj.at_post_puppet.call_args)
def test_puppet_object_no_permission(self):
@ -247,7 +276,12 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler
account = create.create_account(f"TestAccount{randint(0, 999999)}", email="test@test.com", password="testpassword", typeclass=DefaultAccount)
account = create.create_account(
f"TestAccount{randint(0, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
@ -257,7 +291,9 @@ class TestDefaultAccount(TestCase):
account.puppet_object(self.s1, obj)
self.assertTrue(self.s1.data_out.call_args[1]['text'].startswith("You don't have permission to puppet"))
self.assertTrue(
self.s1.data_out.call_args[1]["text"].startswith("You don't have permission to puppet")
)
self.assertIsNone(obj.at_post_puppet.call_args)
@override_settings(MULTISESSION_MODE=0)
@ -266,7 +302,12 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler
account = create.create_account(f"TestAccount{randint(0, 999999)}", email="test@test.com", password="testpassword", typeclass=DefaultAccount)
account = create.create_account(
f"TestAccount{randint(0, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
@ -281,7 +322,9 @@ class TestDefaultAccount(TestCase):
account.puppet_object(self.s1, obj)
# works because django.conf.settings.MULTISESSION_MODE is not in (1, 3)
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions.|n"))
self.assertTrue(
self.s1.data_out.call_args[1]["text"].endswith("from another of your sessions.|n")
)
self.assertTrue(obj.at_post_puppet.call_args[1] == {})
def test_puppet_object_already_puppeted(self):
@ -289,7 +332,12 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler
account = create.create_account(f"TestAccount{randint(0, 999999)}", email="test@test.com", password="testpassword", typeclass=DefaultAccount)
account = create.create_account(
f"TestAccount{randint(0, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.account = account
self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
@ -304,28 +352,33 @@ class TestDefaultAccount(TestCase):
obj.at_post_puppet = Mock()
account.puppet_object(self.s1, obj)
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("is already puppeted by another Account."))
self.assertTrue(
self.s1.data_out.call_args[1]["text"].endswith(
"is already puppeted by another Account."
)
)
self.assertIsNone(obj.at_post_puppet.call_args)
class TestAccountPuppetDeletion(EvenniaTest):
@override_settings(MULTISESSION_MODE=2)
def test_puppet_deletion(self):
# Check for existing chars
self.assertFalse(self.account.db._playable_characters,
'Account should not have any chars by default.')
self.assertFalse(
self.account.db._playable_characters, "Account should not have any chars by default."
)
# Add char1 to account's playable characters
self.account.db._playable_characters.append(self.char1)
self.assertTrue(self.account.db._playable_characters,
'Char was not added to account.')
self.assertTrue(self.account.db._playable_characters, "Char was not added to account.")
# See what happens when we delete char1.
self.char1.delete()
# Playable char list should be empty.
self.assertFalse(self.account.db._playable_characters,
f'Playable character list is not empty! {self.account.db._playable_characters}')
self.assertFalse(
self.account.db._playable_characters,
f"Playable character list is not empty! {self.account.db._playable_characters}",
)
class TestDefaultAccountEv(EvenniaTest):
@ -333,6 +386,7 @@ class TestDefaultAccountEv(EvenniaTest):
Testing using the EvenniaTest parent
"""
def test_characters_property(self):
"test existence of None in _playable_characters Attr"
self.account.db._playable_characters = [self.char1, None]
@ -353,7 +407,9 @@ class TestDefaultAccountEv(EvenniaTest):
self.assertEqual(idle, 10)
# test no sessions
with patch("evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]) as mock_sessh:
with patch(
"evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]
) as mock_sessh:
idle = self.account.idle_time
self.assertEqual(idle, None)
@ -364,18 +420,21 @@ class TestDefaultAccountEv(EvenniaTest):
self.assertEqual(conn, 10)
# test no sessions
with patch("evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]) as mock_sessh:
with patch(
"evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]
) as mock_sessh:
idle = self.account.connection_time
self.assertEqual(idle, None)
def test_create_account(self):
acct = create.account(
"TestAccount3", "test@test.com", "testpassword123",
"TestAccount3",
"test@test.com",
"testpassword123",
locks="test:all()",
tags=[("tag1", "category1"), ("tag2", "category2", "data1"), ("tag3", None)],
attributes=[("key1", "value1", "category1",
"edit:false()", True),
("key2", "value2")])
attributes=[("key1", "value1", "category1", "edit:false()", True), ("key2", "value2")],
)
acct.save()
self.assertTrue(acct.pk)
@ -389,7 +448,7 @@ class TestDefaultAccountEv(EvenniaTest):
ret = self.account.at_look(target=self.obj1, session=self.session)
self.assertTrue("Obj" in ret)
ret = self.account.at_look(target="Invalid", session=self.session)
self.assertEqual(ret, 'Invalid has no in-game appearance.')
self.assertEqual(ret, "Invalid has no in-game appearance.")
def test_msg(self):
self.account.msg