mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Upgrade evennia dependencies and RUN MIGRATIONS. Update requirement to Django 5.3, which has some backwards-incompatible index changes
This commit is contained in:
parent
4269745d3e
commit
0f28eb1ac3
12 changed files with 701 additions and 588 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
## Main branch
|
||||
|
||||
Updated dependencies: Twisted >24 (<25). Python 3.10, 3.11, 3.12, 3.13. Will
|
||||
drop 3.10 support as part of next major release.
|
||||
Updated dependencies: Django >5.1 (<5,2), Twisted >24 (<25).
|
||||
Python versions: 3.11, 3.12, 3.13.
|
||||
|
||||
- Feat (backwards incompatible): RUN MIGRATIONS (`evennia migrate`): Now requiring Django 5.1 (Griatch)
|
||||
- Feat (backwards incompatible): Drop support and testing for Python 3.10 (Griatch)
|
||||
- [Feat][pull3719]: Support Python 3.13. (0xDEADFED5)
|
||||
- [Feat][pull3633]: Default object's default descs are now taken from a `default_description`
|
||||
class variable instead of the `desc` Attribute always being set (count-infinity)
|
||||
|
|
@ -13,8 +15,7 @@ drop 3.10 support as part of next major release.
|
|||
strings instead of `None` if no name is provided, also enforce string type (InspectorCaracal)
|
||||
- [Fix][pull3682]: Allow in-game help searching for commands natively starting
|
||||
with `*` (which is the Lunr search wildcard) (count-infinity)
|
||||
- [Fix][pull3684]: Web client stopped auto-focusing the input box after opening
|
||||
settings (count-infinity)
|
||||
- [Fix][pull3684]: Web client stopped auto-focusing the input box after opening settings (count-infinity)
|
||||
- [Fix][pull3689]: Partial matching fix in default search, makes sure e.g. `b sw` uniquely
|
||||
finds `big sword` even if another type of sword is around (InspectorCaracal)
|
||||
- [Fix][pull3690]: In searches, allow special 'here' and 'me' keywords only be valid queries
|
||||
|
|
@ -37,7 +38,6 @@ drop 3.10 support as part of next major release.
|
|||
used as the task's category (Griatch)
|
||||
- [Docs]: Fixes from InspectorCaracal, Griatch, ChrisLR
|
||||
|
||||
|
||||
[pull3633]: https://github.com/evennia/evennia/pull/3633
|
||||
[pull3677]: https://github.com/evennia/evennia/pull/3677
|
||||
[pull3682]: https://github.com/evennia/evennia/pull/3682
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
# when people upgrade outside regular channels). This file only supports lines of
|
||||
# `value = number` and only specific names supported by the handler.
|
||||
|
||||
PYTHON_MIN = 3.10
|
||||
PYTHON_MIN = 3.11
|
||||
PYTHON_MAX_TESTED = 3.13.100
|
||||
TWISTED_MIN = 24.11
|
||||
DJANGO_MIN = 4.0.2
|
||||
DJANGO_MAX_TESTED = 4.2.100
|
||||
DJANGO_MAX_TESTED = 5.1.100
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from django.conf import settings
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.files.base import ContentFile
|
||||
from django.test import TestCase, override_settings
|
||||
from django.utils.timezone import is_aware, utc
|
||||
from django.utils.timezone import is_aware
|
||||
|
||||
_SKIP = False
|
||||
try:
|
||||
|
|
@ -533,7 +533,7 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
|
||||
def _test_storage_mtime(self, use_tz):
|
||||
obj = self.storage.bucket.Object.return_value
|
||||
obj.last_modified = datetime.datetime.now(utc)
|
||||
obj.last_modified = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
name = "file.txt"
|
||||
self.assertFalse(
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
This directory contains Evennia's log files. The existence of this README.md file is also necessary
|
||||
to correctly include the log directory in git (since log files are ignored by git and you can't
|
||||
commit an empty directory).
|
||||
commit an empty directory).
|
||||
|
||||
- `server.log` - log file from the game Server.
|
||||
- `portal.log` - log file from Portal proxy (internet facing)
|
||||
`server.log` - log file from the game Server.
|
||||
`portal.log` - log file from Portal proxy (internet facing)
|
||||
|
||||
Usually these logs are viewed together with `evennia -l`. They are also rotated every week so as not
|
||||
to be too big. Older log names will have a name appended by `_month_date`.
|
||||
|
||||
- `lockwarnings.log` - warnings from the lock system.
|
||||
- `http_requests.log` - this will generally be empty unless turning on debugging inside the server.
|
||||
to be too big. Older log names will have a name appended by `_month_date`.
|
||||
|
||||
- `channel_<channelname>.log` - these are channel logs for the in-game channels They are also used
|
||||
by the `/history` flag in-game to get the latest message history.
|
||||
`lockwarnings.log` - warnings from the lock system.
|
||||
`http_requests.log` - this will generally be empty unless turning on debugging inside the server.
|
||||
|
||||
`channel_<channelname>.log` - these are channel logs for the in-game channels They are also used
|
||||
by the `/history` flag in-game to get the latest message history.
|
||||
|
|
|
|||
|
|
@ -1484,69 +1484,78 @@ def create_superuser():
|
|||
|
||||
def check_database(always_return=False):
|
||||
"""
|
||||
Check so the database exists.
|
||||
Check if the database exists and has basic tables. This is only run by the launcher.
|
||||
|
||||
Args:
|
||||
always_return (bool, optional): If set, will always return True/False
|
||||
also on critical errors. No output will be printed.
|
||||
always_return (bool, optional): If True, will not raise exceptions on errors.
|
||||
|
||||
Returns:
|
||||
exists (bool): `True` if the database exists, otherwise `False`.
|
||||
|
||||
|
||||
exists (bool): `True` if database exists and seems set up, `False` otherwise.
|
||||
If `always_return` is `False`, this will raise exceptions instead of
|
||||
returning `False`.
|
||||
"""
|
||||
# Check so a database exists and is accessible
|
||||
# Check if database exists
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
|
||||
tables = connection.introspection.get_table_list(connection.cursor())
|
||||
if not tables or not isinstance(tables[0], str): # django 1.8+
|
||||
tables = [tableinfo.name for tableinfo in tables]
|
||||
if tables and "accounts_accountdb" in tables:
|
||||
# database exists and seems set up. Initialize evennia.
|
||||
evennia._init()
|
||||
# Try to get Account#1
|
||||
from evennia.accounts.models import AccountDB
|
||||
tables_to_check = [
|
||||
"accounts_accountdb", # base account table
|
||||
"objects_objectdb", # base object table
|
||||
"scripts_scriptdb", # base script table
|
||||
"typeclasses_tag", # base tag table
|
||||
]
|
||||
|
||||
try:
|
||||
AccountDB.objects.get(id=1)
|
||||
except (django.db.utils.OperationalError, ProgrammingError) as e:
|
||||
if always_return:
|
||||
return False
|
||||
print(ERROR_DATABASE.format(traceback=e))
|
||||
sys.exit()
|
||||
except AccountDB.DoesNotExist:
|
||||
# no superuser yet. We need to create it.
|
||||
with connection.cursor() as cursor:
|
||||
# Get all table names in the database
|
||||
if connection.vendor == "postgresql":
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT tablename FROM pg_tables
|
||||
WHERE schemaname = 'public'
|
||||
"""
|
||||
)
|
||||
elif connection.vendor == "mysql":
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = %s
|
||||
""",
|
||||
[settings.DATABASES["default"]["NAME"]],
|
||||
)
|
||||
elif connection.vendor == "sqlite":
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
||||
"""
|
||||
)
|
||||
else:
|
||||
if not always_return:
|
||||
raise Exception(f"Unsupported database vendor: {connection.vendor}")
|
||||
return False
|
||||
|
||||
other_superuser = AccountDB.objects.filter(is_superuser=True)
|
||||
if other_superuser:
|
||||
# Another superuser was found, but not with id=1. This may
|
||||
# happen if using flush (the auto-id starts at a higher
|
||||
# value). Wwe copy this superuser into id=1. To do
|
||||
# this we must deepcopy it, delete it then save the copy
|
||||
# with the new id. This allows us to avoid the UNIQUE
|
||||
# constraint on usernames.
|
||||
other = other_superuser[0]
|
||||
other_id = other.id
|
||||
other_key = other.username
|
||||
print(WARNING_MOVING_SUPERUSER.format(other_key=other_key, other_id=other_id))
|
||||
res = ""
|
||||
while res.upper() != "Y":
|
||||
# ask for permission
|
||||
res = eval(input("Continue [Y]/N: "))
|
||||
if res.upper() == "N":
|
||||
sys.exit()
|
||||
elif not res:
|
||||
break
|
||||
# continue with the
|
||||
from copy import deepcopy
|
||||
existing_tables = {row[0].lower() for row in cursor.fetchall()}
|
||||
|
||||
new = deepcopy(other)
|
||||
other.delete()
|
||||
new.id = 1
|
||||
new.save()
|
||||
else:
|
||||
create_superuser()
|
||||
check_database(always_return=always_return)
|
||||
return True
|
||||
# Check if essential tables exist
|
||||
missing_tables = [table for table in tables_to_check if table not in existing_tables]
|
||||
|
||||
if missing_tables:
|
||||
if always_return:
|
||||
return False
|
||||
raise Exception(
|
||||
f"Database tables missing: {', '.join(missing_tables)}. "
|
||||
"Did you remember to run migrations?"
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as exc:
|
||||
if not always_return:
|
||||
raise
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def getenv():
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# Generated by Django 5.1.6 on 2025-03-01 21:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("typeclasses", "0016_alter_attribute_id_alter_tag_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# First create the index with the old name that Django expects
|
||||
migrations.AddIndex(
|
||||
model_name="tag",
|
||||
index=models.Index(
|
||||
fields=["db_key", "db_category", "db_tagtype", "db_model"],
|
||||
name="typeclasses_tag_db_key_db_category_db_tagtype_db_model_idx",
|
||||
),
|
||||
),
|
||||
# Then rename it to the new name
|
||||
migrations.RenameIndex(
|
||||
model_name="tag",
|
||||
new_name="typeclasses_db_key_be0c81_idx",
|
||||
old_fields=("db_key", "db_category", "db_tagtype", "db_model"),
|
||||
),
|
||||
]
|
||||
|
|
@ -14,7 +14,6 @@ from collections import defaultdict
|
|||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from evennia.locks.lockfuncs import perm as perm_lockfunc
|
||||
from evennia.utils.utils import make_iter, to_str
|
||||
|
||||
|
|
@ -79,9 +78,10 @@ class Tag(models.Model):
|
|||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
|
||||
verbose_name = "Tag"
|
||||
unique_together = (("db_key", "db_category", "db_tagtype", "db_model"),)
|
||||
index_together = (("db_key", "db_category", "db_tagtype", "db_model"),)
|
||||
indexes = [models.Index(fields=["db_key", "db_category", "db_tagtype", "db_model"])]
|
||||
|
||||
def __lt__(self, other):
|
||||
return str(self) < str(other)
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ objects already existing in the database.
|
|||
|
||||
"""
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
|
||||
# limit symbol import from API
|
||||
__all__ = (
|
||||
|
|
@ -29,232 +28,254 @@ __all__ = (
|
|||
|
||||
_GA = object.__getattribute__
|
||||
|
||||
# import objects this way to avoid circular import problems
|
||||
try:
|
||||
ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class()
|
||||
ScriptDB = ContentType.objects.get(app_label="scripts", model="scriptdb").model_class()
|
||||
AccountDB = ContentType.objects.get(app_label="accounts", model="accountdb").model_class()
|
||||
Msg = ContentType.objects.get(app_label="comms", model="msg").model_class()
|
||||
ChannelDB = ContentType.objects.get(app_label="comms", model="channeldb").model_class()
|
||||
HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_class()
|
||||
Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class()
|
||||
except (OperationalError, ProgrammingError):
|
||||
# this is a fallback used during tests/doc building
|
||||
print("Database not available yet - using temporary fallback for create managers.")
|
||||
from evennia.accounts.models import AccountDB
|
||||
from evennia.comms.models import ChannelDB, Msg
|
||||
from evennia.help.models import HelpEntry
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.scripts.models import ScriptDB
|
||||
from evennia.typeclasses.tags import Tag # noqa
|
||||
|
||||
#
|
||||
# Game Object creation
|
||||
#
|
||||
# Create a new in-game object.
|
||||
#
|
||||
# Keyword Args:
|
||||
# typeclass (class or str): Class or python path to a typeclass.
|
||||
# key (str): Name of the new object. If not set, a name of
|
||||
# `#dbref` will be set.
|
||||
# location (Object or str): Obj or #dbref to use as the location of the new object.
|
||||
# home (Object or str): Obj or #dbref to use as the object's home location.
|
||||
# permissions (list): A list of permission strings or tuples (permstring, category).
|
||||
# locks (str): one or more lockstrings, separated by semicolons.
|
||||
# aliases (list): A list of alternative keys or tuples (aliasstring, category).
|
||||
# tags (list): List of tag keys or tuples (tagkey, category) or (tagkey, category, data).
|
||||
# destination (Object or str): Obj or #dbref to use as an Exit's target.
|
||||
# report_to (Object): The object to return error messages to.
|
||||
# nohome (bool): This allows the creation of objects without a
|
||||
# default home location; only used when creating the default
|
||||
# location itself or during unittests.
|
||||
# attributes (list): Tuples on the form (key, value) or (key, value, category),
|
||||
# (key, value, lockstring) or (key, value, lockstring, default_access).
|
||||
# to set as Attributes on the new object.
|
||||
# nattributes (list): Non-persistent tuples on the form (key, value). Note that
|
||||
# adding this rarely makes sense since this data will not survive a reload.
|
||||
#
|
||||
# Returns:
|
||||
# object (Object): A newly created object of the given typeclass.
|
||||
#
|
||||
# Raises:
|
||||
# ObjectDB.DoesNotExist: If trying to create an Object with
|
||||
# `location` or `home` that can't be found.
|
||||
#
|
||||
# Lazy-loaded model classes
|
||||
def _get_objectdb():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
create_object = ObjectDB.objects.create_object
|
||||
# alias for create_object
|
||||
return ContentType.objects.get(app_label="objects", model="objectdb").model_class()
|
||||
|
||||
|
||||
def _get_scriptdb():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="scripts", model="scriptdb").model_class()
|
||||
|
||||
|
||||
def _get_accountdb():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="accounts", model="accountdb").model_class()
|
||||
|
||||
|
||||
def _get_msg():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="comms", model="msg").model_class()
|
||||
|
||||
|
||||
def _get_channeldb():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="comms", model="channeldb").model_class()
|
||||
|
||||
|
||||
def _get_helpentry():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="help", model="helpentry").model_class()
|
||||
|
||||
|
||||
def _get_tag():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="typeclasses", model="tag").model_class()
|
||||
|
||||
|
||||
# Lazy model instances
|
||||
ObjectDB = SimpleLazyObject(_get_objectdb)
|
||||
ScriptDB = SimpleLazyObject(_get_scriptdb)
|
||||
AccountDB = SimpleLazyObject(_get_accountdb)
|
||||
Msg = SimpleLazyObject(_get_msg)
|
||||
ChannelDB = SimpleLazyObject(_get_channeldb)
|
||||
HelpEntry = SimpleLazyObject(_get_helpentry)
|
||||
Tag = SimpleLazyObject(_get_tag)
|
||||
|
||||
|
||||
def create_object(*args, **kwargs):
|
||||
"""
|
||||
Create a new in-game object.
|
||||
|
||||
Keyword Args:
|
||||
typeclass (class or str): Class or python path to a typeclass.
|
||||
key (str): Name of the new object. If not set, a name of
|
||||
`#dbref` will be set.
|
||||
location (Object or str): Obj or #dbref to use as the location of the new object.
|
||||
home (Object or str): Obj or #dbref to use as the object's home location.
|
||||
permissions (list): A list of permission strings or tuples (permstring, category).
|
||||
locks (str): one or more lockstrings, separated by semicolons.
|
||||
aliases (list): A list of alternative keys or tuples (aliasstring, category).
|
||||
tags (list): List of tag keys or tuples (tagkey, category) or (tagkey, category, data).
|
||||
destination (Object or str): Obj or #dbref to use as an Exit's target.
|
||||
report_to (Object): The object to return error messages to.
|
||||
nohome (bool): This allows the creation of objects without a
|
||||
default home location; only used when creating the default
|
||||
location itself or during unittests.
|
||||
attributes (list): Tuples on the form (key, value) or (key, value, category),
|
||||
(key, value, lockstring) or (key, value, lockstring, default_access).
|
||||
to set as Attributes on the new object.
|
||||
nattributes (list): Non-persistent tuples on the form (key, value). Note that
|
||||
adding this rarely makes sense since this data will not survive a reload.
|
||||
|
||||
Returns:
|
||||
object (Object): A newly created object of the given typeclass.
|
||||
|
||||
Raises:
|
||||
ObjectDB.DoesNotExist: If trying to create an Object with
|
||||
`location` or `home` that can't be found.
|
||||
"""
|
||||
return ObjectDB.objects.create_object(*args, **kwargs)
|
||||
|
||||
|
||||
def create_script(*args, **kwargs):
|
||||
"""
|
||||
Create a new script. All scripts are a combination of a database
|
||||
object that communicates with the database, and an typeclass that
|
||||
'decorates' the database object into being different types of
|
||||
scripts. It's behaviour is similar to the game objects except
|
||||
scripts has a time component and are more limited in scope.
|
||||
|
||||
Keyword Args:
|
||||
typeclass (class or str): Class or python path to a typeclass.
|
||||
key (str): Name of the new object. If not set, a name of
|
||||
#dbref will be set.
|
||||
obj (Object): The entity on which this Script sits. If this
|
||||
is `None`, we are creating a "global" script.
|
||||
account (Account): The account on which this Script sits. It is
|
||||
exclusiv to `obj`.
|
||||
locks (str): one or more lockstrings, separated by semicolons.
|
||||
interval (int): The triggering interval for this Script, in
|
||||
seconds. If unset, the Script will not have a timing
|
||||
component.
|
||||
start_delay (bool): If `True`, will wait `interval` seconds
|
||||
before triggering the first time.
|
||||
repeats (int): The number of times to trigger before stopping.
|
||||
If unset, will repeat indefinitely.
|
||||
persistent (bool): If this Script survives a server shutdown
|
||||
or not (all Scripts will survive a reload).
|
||||
autostart (bool): If this Script will start immediately when
|
||||
created or if the `start` method must be called explicitly.
|
||||
report_to (Object): The object to return error messages to.
|
||||
desc (str): Optional description of script
|
||||
tags (list): List of tags or tuples (tag, category).
|
||||
attributes (list): List if tuples (key, value) or (key, value, category)
|
||||
(key, value, lockstring) or (key, value, lockstring, default_access).
|
||||
|
||||
Returns:
|
||||
script (obj): An instance of the script created
|
||||
|
||||
See evennia.scripts.manager for methods to manipulate existing
|
||||
scripts in the database.
|
||||
"""
|
||||
return ScriptDB.objects.create_script(*args, **kwargs)
|
||||
|
||||
|
||||
def create_help_entry(*args, **kwargs):
|
||||
"""
|
||||
Create a static help entry in the help database. Note that Command
|
||||
help entries are dynamic and directly taken from the __doc__
|
||||
entries of the command. The database-stored help entries are
|
||||
intended for more general help on the game, more extensive info,
|
||||
in-game setting information and so on.
|
||||
|
||||
Args:
|
||||
key (str): The name of the help entry.
|
||||
entrytext (str): The body of te help entry
|
||||
category (str, optional): The help category of the entry.
|
||||
locks (str, optional): A lockstring to restrict access.
|
||||
aliases (list of str, optional): List of alternative (likely shorter) keynames.
|
||||
tags (lst, optional): List of tags or tuples `(tag, category)`.
|
||||
|
||||
Returns:
|
||||
help (HelpEntry): A newly created help entry.
|
||||
"""
|
||||
return HelpEntry.objects.create_help(*args, **kwargs)
|
||||
|
||||
|
||||
def create_message(*args, **kwargs):
|
||||
"""
|
||||
Create a new communication Msg. Msgs represent a unit of
|
||||
database-persistent communication between entites.
|
||||
|
||||
Args:
|
||||
senderobj (Object, Account, Script, str or list): The entity (or
|
||||
entities) sending the Msg. If a `str`, this is the id-string
|
||||
for an external sender type.
|
||||
message (str): Text with the message. Eventual headers, titles
|
||||
etc should all be included in this text string. Formatting
|
||||
will be retained.
|
||||
receivers (Object, Account, Script, str or list): An Account/Object to send
|
||||
to, or a list of them. If a string, it's an identifier for an external
|
||||
receiver.
|
||||
locks (str): Lock definition string.
|
||||
tags (list): A list of tags or tuples `(tag, category)`.
|
||||
header (str): Mime-type or other optional information for the message
|
||||
|
||||
Notes:
|
||||
The Comm system is created to be very open-ended, so it's fully
|
||||
possible to let a message both go several receivers at the same time,
|
||||
it's up to the command definitions to limit this as desired.
|
||||
"""
|
||||
return Msg.objects.create_message(*args, **kwargs)
|
||||
|
||||
|
||||
def create_channel(*args, **kwargs):
|
||||
"""
|
||||
Create A communication Channel. A Channel serves as a central hub
|
||||
for distributing Msgs to groups of people without specifying the
|
||||
receivers explicitly. Instead accounts may 'connect' to the channel
|
||||
and follow the flow of messages. By default the channel allows
|
||||
access to all old messages, but this can be turned off with the
|
||||
keep_log switch.
|
||||
|
||||
Args:
|
||||
key (str): This must be unique.
|
||||
|
||||
Keyword Args:
|
||||
aliases (list of str): List of alternative (likely shorter) keynames.
|
||||
desc (str): A description of the channel, for use in listings.
|
||||
locks (str): Lockstring.
|
||||
keep_log (bool): Log channel throughput.
|
||||
typeclass (str or class): The typeclass of the Channel (not
|
||||
often used).
|
||||
tags (list): A list of tags or tuples `(tag[,category[,data]])`.
|
||||
attrs (list): List of attributes on form `(name, value[,category[,lockstring]])`
|
||||
|
||||
Returns:
|
||||
channel (Channel): A newly created channel.
|
||||
"""
|
||||
return ChannelDB.objects.create_channel(*args, **kwargs)
|
||||
|
||||
|
||||
def create_account(*args, **kwargs):
|
||||
"""
|
||||
This creates a new account.
|
||||
|
||||
Args:
|
||||
key (str): The account's name. This should be unique.
|
||||
email (str or None): Email on valid addr@addr.domain form. If
|
||||
the empty string, will be set to None.
|
||||
password (str): Password in cleartext.
|
||||
|
||||
Keyword Args:
|
||||
typeclass (str): The typeclass to use for the account.
|
||||
is_superuser (bool): Whether or not this account is to be a superuser
|
||||
locks (str): Lockstring.
|
||||
permission (list): List of permission strings.
|
||||
tags (list): List of Tags on form `(key, category[, data])`
|
||||
attributes (list): List of Attributes on form
|
||||
`(key, value [, category, [,lockstring [, default_pass]]])`
|
||||
report_to (Object): An object with a msg() method to report
|
||||
errors to. If not given, errors will be logged.
|
||||
|
||||
Returns:
|
||||
Account: The newly created Account.
|
||||
Raises:
|
||||
ValueError: If `key` already exists in database.
|
||||
|
||||
Notes:
|
||||
Usually only the server admin should need to be superuser, all
|
||||
other access levels can be handled with more fine-grained
|
||||
permissions or groups. A superuser bypasses all lock checking
|
||||
operations and is thus not suitable for play-testing the game.
|
||||
"""
|
||||
return AccountDB.objects.create_account(*args, **kwargs)
|
||||
|
||||
|
||||
# Aliases for the creation functions
|
||||
object = create_object
|
||||
|
||||
|
||||
#
|
||||
# Script creation
|
||||
|
||||
# Create a new script. All scripts are a combination of a database
|
||||
# object that communicates with the database, and an typeclass that
|
||||
# 'decorates' the database object into being different types of
|
||||
# scripts. It's behaviour is similar to the game objects except
|
||||
# scripts has a time component and are more limited in scope.
|
||||
#
|
||||
# Keyword Args:
|
||||
# typeclass (class or str): Class or python path to a typeclass.
|
||||
# key (str): Name of the new object. If not set, a name of
|
||||
# #dbref will be set.
|
||||
# obj (Object): The entity on which this Script sits. If this
|
||||
# is `None`, we are creating a "global" script.
|
||||
# account (Account): The account on which this Script sits. It is
|
||||
# exclusiv to `obj`.
|
||||
# locks (str): one or more lockstrings, separated by semicolons.
|
||||
# interval (int): The triggering interval for this Script, in
|
||||
# seconds. If unset, the Script will not have a timing
|
||||
# component.
|
||||
# start_delay (bool): If `True`, will wait `interval` seconds
|
||||
# before triggering the first time.
|
||||
# repeats (int): The number of times to trigger before stopping.
|
||||
# If unset, will repeat indefinitely.
|
||||
# persistent (bool): If this Script survives a server shutdown
|
||||
# or not (all Scripts will survive a reload).
|
||||
# autostart (bool): If this Script will start immediately when
|
||||
# created or if the `start` method must be called explicitly.
|
||||
# report_to (Object): The object to return error messages to.
|
||||
# desc (str): Optional description of script
|
||||
# tags (list): List of tags or tuples (tag, category).
|
||||
# attributes (list): List if tuples (key, value) or (key, value, category)
|
||||
# (key, value, lockstring) or (key, value, lockstring, default_access).
|
||||
#
|
||||
# Returns:
|
||||
# script (obj): An instance of the script created
|
||||
#
|
||||
# See evennia.scripts.manager for methods to manipulate existing
|
||||
# scripts in the database.
|
||||
|
||||
create_script = ScriptDB.objects.create_script
|
||||
# alias
|
||||
script = create_script
|
||||
|
||||
|
||||
#
|
||||
# Help entry creation
|
||||
#
|
||||
|
||||
# """
|
||||
# Create a static help entry in the help database. Note that Command
|
||||
# help entries are dynamic and directly taken from the __doc__
|
||||
# entries of the command. The database-stored help entries are
|
||||
# intended for more general help on the game, more extensive info,
|
||||
# in-game setting information and so on.
|
||||
#
|
||||
# Args:
|
||||
# key (str): The name of the help entry.
|
||||
# entrytext (str): The body of te help entry
|
||||
# category (str, optional): The help category of the entry.
|
||||
# locks (str, optional): A lockstring to restrict access.
|
||||
# aliases (list of str, optional): List of alternative (likely shorter) keynames.
|
||||
# tags (lst, optional): List of tags or tuples `(tag, category)`.
|
||||
#
|
||||
# Returns:
|
||||
# help (HelpEntry): A newly created help entry.
|
||||
#
|
||||
|
||||
create_help_entry = HelpEntry.objects.create_help
|
||||
# alias
|
||||
help_entry = create_help_entry
|
||||
|
||||
|
||||
#
|
||||
# Comm system methods
|
||||
|
||||
#
|
||||
# Create a new communication Msg. Msgs represent a unit of
|
||||
# database-persistent communication between entites.
|
||||
#
|
||||
# Args:
|
||||
# senderobj (Object, Account, Script, str or list): The entity (or
|
||||
# entities) sending the Msg. If a `str`, this is the id-string
|
||||
# for an external sender type.
|
||||
# message (str): Text with the message. Eventual headers, titles
|
||||
# etc should all be included in this text string. Formatting
|
||||
# will be retained.
|
||||
# receivers (Object, Account, Script, str or list): An Account/Object to send
|
||||
# to, or a list of them. If a string, it's an identifier for an external
|
||||
# receiver.
|
||||
# locks (str): Lock definition string.
|
||||
# tags (list): A list of tags or tuples `(tag, category)`.
|
||||
# header (str): Mime-type or other optional information for the message
|
||||
#
|
||||
# Notes:
|
||||
# The Comm system is created to be very open-ended, so it's fully
|
||||
# possible to let a message both go several receivers at the same time,
|
||||
# it's up to the command definitions to limit this as desired.
|
||||
#
|
||||
|
||||
create_message = Msg.objects.create_message
|
||||
message = create_message
|
||||
create_msg = create_message
|
||||
|
||||
|
||||
# Create A communication Channel. A Channel serves as a central hub
|
||||
# for distributing Msgs to groups of people without specifying the
|
||||
# receivers explicitly. Instead accounts may 'connect' to the channel
|
||||
# and follow the flow of messages. By default the channel allows
|
||||
# access to all old messages, but this can be turned off with the
|
||||
# keep_log switch.
|
||||
#
|
||||
# Args:
|
||||
# key (str): This must be unique.
|
||||
#
|
||||
# Keyword Args:
|
||||
# aliases (list of str): List of alternative (likely shorter) keynames.
|
||||
# desc (str): A description of the channel, for use in listings.
|
||||
# locks (str): Lockstring.
|
||||
# keep_log (bool): Log channel throughput.
|
||||
# typeclass (str or class): The typeclass of the Channel (not
|
||||
# often used).
|
||||
# tags (list): A list of tags or tuples `(tag, category)`.
|
||||
#
|
||||
# Returns:
|
||||
# channel (Channel): A newly created channel.
|
||||
#
|
||||
|
||||
create_channel = ChannelDB.objects.create_channel
|
||||
channel = create_channel
|
||||
|
||||
|
||||
#
|
||||
# Account creation methods
|
||||
#
|
||||
|
||||
# This creates a new account.
|
||||
#
|
||||
# Args:
|
||||
# key (str): The account's name. This should be unique.
|
||||
# email (str or None): Email on valid addr@addr.domain form. If
|
||||
# the empty string, will be set to None.
|
||||
# password (str): Password in cleartext.
|
||||
#
|
||||
# Keyword Args:
|
||||
# typeclass (str): The typeclass to use for the account.
|
||||
# is_superuser (bool): Wether or not this account is to be a superuser
|
||||
# locks (str): Lockstring.
|
||||
# permission (list): List of permission strings.
|
||||
# tags (list): List of Tags on form `(key, category[, data])`
|
||||
# attributes (list): List of Attributes on form
|
||||
# `(key, value [, category, [,lockstring [, default_pass]]])`
|
||||
# report_to (Object): An object with a msg() method to report
|
||||
# errors to. If not given, errors will be logged.
|
||||
#
|
||||
# Returns:
|
||||
# Account: The newly created Account.
|
||||
# Raises:
|
||||
# ValueError: If `key` already exists in database.
|
||||
#
|
||||
#
|
||||
# Notes:
|
||||
# Usually only the server admin should need to be superuser, all
|
||||
# other access levels can be handled with more fine-grained
|
||||
# permissions or groups. A superuser bypasses all lock checking
|
||||
# operations and is thus not suitable for play-testing the game.
|
||||
|
||||
create_account = AccountDB.objects.create_account
|
||||
# alias
|
||||
account = create_account
|
||||
|
|
|
|||
|
|
@ -21,13 +21,9 @@ Example: To reach the search method 'get_object_with_account'
|
|||
> from evennia.objects.models import ObjectDB
|
||||
> match = Object.objects.get_object_with_account(...)
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# Import the manager methods to be wrapped
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
|
||||
# limit symbol import from API
|
||||
__all__ = (
|
||||
|
|
@ -45,180 +41,265 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
# import objects this way to avoid circular import problems
|
||||
try:
|
||||
ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class()
|
||||
AccountDB = ContentType.objects.get(app_label="accounts", model="accountdb").model_class()
|
||||
ScriptDB = ContentType.objects.get(app_label="scripts", model="scriptdb").model_class()
|
||||
Msg = ContentType.objects.get(app_label="comms", model="msg").model_class()
|
||||
ChannelDB = ContentType.objects.get(app_label="comms", model="channeldb").model_class()
|
||||
HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_class()
|
||||
Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class()
|
||||
except (OperationalError, ProgrammingError):
|
||||
# this is a fallback used during tests/doc building
|
||||
print("Database not available yet - using temporary fallback for search managers.")
|
||||
from evennia.accounts.models import AccountDB
|
||||
from evennia.comms.models import ChannelDB, Msg
|
||||
from evennia.help.models import HelpEntry
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.scripts.models import ScriptDB
|
||||
from evennia.typeclasses.tags import Tag # noqa
|
||||
# Lazy-loaded model classes
|
||||
def _get_objectdb():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="objects", model="objectdb").model_class()
|
||||
|
||||
|
||||
def _get_accountdb():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="accounts", model="accountdb").model_class()
|
||||
|
||||
|
||||
def _get_scriptdb():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="scripts", model="scriptdb").model_class()
|
||||
|
||||
|
||||
def _get_msg():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="comms", model="msg").model_class()
|
||||
|
||||
|
||||
def _get_channeldb():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="comms", model="channeldb").model_class()
|
||||
|
||||
|
||||
def _get_helpentry():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="help", model="helpentry").model_class()
|
||||
|
||||
|
||||
def _get_tag():
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get(app_label="typeclasses", model="tag").model_class()
|
||||
|
||||
|
||||
# Lazy model instances
|
||||
ObjectDB = SimpleLazyObject(_get_objectdb)
|
||||
AccountDB = SimpleLazyObject(_get_accountdb)
|
||||
ScriptDB = SimpleLazyObject(_get_scriptdb)
|
||||
Msg = SimpleLazyObject(_get_msg)
|
||||
ChannelDB = SimpleLazyObject(_get_channeldb)
|
||||
HelpEntry = SimpleLazyObject(_get_helpentry)
|
||||
Tag = SimpleLazyObject(_get_tag)
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Search manager-wrappers
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# Search objects as a character
|
||||
#
|
||||
# NOTE: A more powerful wrapper of this method
|
||||
# is reachable from within each command class
|
||||
# by using self.caller.search()!
|
||||
#
|
||||
# def object_search(self, ostring=None,
|
||||
# attribute_name=None,
|
||||
# typeclass=None,
|
||||
# candidates=None,
|
||||
# exact=True):
|
||||
#
|
||||
# Search globally or in a list of candidates and return results.
|
||||
# The result is always a list of Objects (or the empty list)
|
||||
#
|
||||
# Arguments:
|
||||
# ostring: (str) The string to compare names against. By default (if
|
||||
# not attribute_name is set), this will search object.key
|
||||
# and object.aliases in order. Can also be on the form #dbref,
|
||||
# which will, if exact=True be matched against primary key.
|
||||
# attribute_name: (str): Use this named ObjectAttribute to match ostring
|
||||
# against, instead of the defaults.
|
||||
# typeclass (str or TypeClass): restrict matches to objects having
|
||||
# this typeclass. This will help speed up global searches.
|
||||
# candidates (list obj ObjectDBs): If supplied, search will only be
|
||||
# performed among the candidates in this list. A common list
|
||||
# of candidates is the contents of the current location.
|
||||
# exact (bool): Match names/aliases exactly or partially. Partial
|
||||
# matching matches the beginning of words in the names/aliases,
|
||||
# using a matching routine to separate multiple matches in
|
||||
# names with multiple components (so "bi sw" will match
|
||||
# "Big sword"). Since this is more expensive than exact
|
||||
# matching, it is recommended to be used together with
|
||||
# the objlist keyword to limit the number of possibilities.
|
||||
# This keyword has no meaning if attribute_name is set.
|
||||
#
|
||||
# Returns:
|
||||
# A list of matching objects (or a list with one unique match)
|
||||
# def object_search(self, ostring, caller=None,
|
||||
# candidates=None,
|
||||
# attribute_name=None):
|
||||
#
|
||||
search_object = ObjectDB.objects.search_object
|
||||
|
||||
def search_object(*args, **kwargs):
|
||||
"""
|
||||
Search for objects in the database.
|
||||
|
||||
Args:
|
||||
key (str or int): Object key or dbref to search for. This can also
|
||||
be a list of keys/dbrefs. `None` (default) returns all objects.
|
||||
exact (bool): Only valid for string keys. If True, requires exact
|
||||
key match, otherwise also match key with case-insensitive and
|
||||
partial matching. Default is True.
|
||||
candidates (list): Only search among these object candidates,
|
||||
if given. Default is to search all objects.
|
||||
attribute_name (str): If set, search by objects with this attribute_name
|
||||
defined on them, with the value specified by `attribute_value`.
|
||||
attribute_value (any): What value the given attribute_name must have.
|
||||
location (Object): Filter by objects at this location.
|
||||
typeclass (str or TypeClass): Filter by objects having this typeclass.
|
||||
This can also be a list of typeclasses.
|
||||
tags (str or list): Filter by objects having one or more Tags.
|
||||
This can be a single tag key, a list of tag keys, or a list of
|
||||
tuples (tag_key, tag_category).
|
||||
nofetch (bool): Don't fetch typeclass and perms data from db.
|
||||
This is faster but gives less info.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Objects matching the search criteria.
|
||||
"""
|
||||
return ObjectDB.objects.search_object(*args, **kwargs)
|
||||
|
||||
|
||||
search_objects = search_object
|
||||
object_search = search_object
|
||||
objects = search_objects
|
||||
|
||||
#
|
||||
# Search for accounts
|
||||
#
|
||||
# account_search(self, ostring)
|
||||
|
||||
# Searches for a particular account by name or
|
||||
# database id.
|
||||
#
|
||||
# ostring = a string or database id.
|
||||
#
|
||||
def search_account(*args, **kwargs):
|
||||
"""
|
||||
Search for accounts in the database.
|
||||
|
||||
Args:
|
||||
key (str or int): Account key or dbref to search for. This can also
|
||||
be a list of keys/dbrefs. `None` (default) returns all accounts.
|
||||
exact (bool): Only valid for string keys. If True, requires exact
|
||||
key match, otherwise also match key with case-insensitive and
|
||||
partial matching. Default is True.
|
||||
candidates (list): Only search among these account candidates,
|
||||
if given. Default is to search all accounts.
|
||||
attribute_name (str): If set, search by accounts with this attribute_name
|
||||
defined on them, with the value specified by `attribute_value`.
|
||||
attribute_value (any): What value the given attribute_name must have.
|
||||
tags (str or list): Filter by accounts having one or more Tags.
|
||||
This can be a single tag key, a list of tag keys, or a list of
|
||||
tuples (tag_key, tag_category).
|
||||
nofetch (bool): Don't fetch typeclass and perms data from db.
|
||||
This is faster but gives less info.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Accounts matching the search criteria.
|
||||
"""
|
||||
return AccountDB.objects.search_account(*args, **kwargs)
|
||||
|
||||
|
||||
search_account = AccountDB.objects.search_account
|
||||
search_accounts = search_account
|
||||
account_search = search_account
|
||||
accounts = search_accounts
|
||||
|
||||
#
|
||||
# Searching for scripts
|
||||
#
|
||||
# script_search(self, ostring, obj=None, only_timed=False)
|
||||
#
|
||||
# Search for a particular script.
|
||||
#
|
||||
# ostring - search criterion - a script ID or key
|
||||
# obj - limit search to scripts defined on this object
|
||||
# only_timed - limit search only to scripts that run
|
||||
# on a timer.
|
||||
#
|
||||
|
||||
search_script = ScriptDB.objects.search_script
|
||||
def search_script(*args, **kwargs):
|
||||
"""
|
||||
Search for scripts in the database.
|
||||
|
||||
Args:
|
||||
key (str or int): Script key or dbref to search for. This can also
|
||||
be a list of keys/dbrefs. `None` (default) returns all scripts.
|
||||
exact (bool): Only valid for string keys. If True, requires exact
|
||||
key match, otherwise also match key with case-insensitive and
|
||||
partial matching. Default is True.
|
||||
candidates (list): Only search among these script candidates,
|
||||
if given. Default is to search all scripts.
|
||||
attribute_name (str): If set, search by scripts with this attribute_name
|
||||
defined on them, with the value specified by `attribute_value`.
|
||||
attribute_value (any): What value the given attribute_name must have.
|
||||
obj (Object): Filter by scripts defined on this object.
|
||||
account (Account): Filter by scripts defined on this account.
|
||||
typeclass (str or TypeClass): Filter by scripts having this typeclass.
|
||||
This can also be a list of typeclasses.
|
||||
tags (str or list): Filter by scripts having one or more Tags.
|
||||
This can be a single tag key, a list of tag keys, or a list of
|
||||
tuples (tag_key, tag_category).
|
||||
nofetch (bool): Don't fetch typeclass and perms data from db.
|
||||
This is faster but gives less info.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Scripts matching the search criteria.
|
||||
"""
|
||||
return ScriptDB.objects.search_script(*args, **kwargs)
|
||||
|
||||
|
||||
search_scripts = search_script
|
||||
script_search = search_script
|
||||
scripts = search_scripts
|
||||
#
|
||||
# Searching for communication messages
|
||||
#
|
||||
#
|
||||
# message_search(self, sender=None, receiver=None, channel=None, freetext=None)
|
||||
#
|
||||
# Search the message database for particular messages. At least one
|
||||
# of the arguments must be given to do a search.
|
||||
#
|
||||
# sender - get messages sent by a particular account
|
||||
# receiver - get messages received by a certain account
|
||||
# channel - get messages sent to a particular channel
|
||||
# freetext - Search for a text string in a message.
|
||||
# NOTE: This can potentially be slow, so make sure to supply
|
||||
# one of the other arguments to limit the search.
|
||||
#
|
||||
|
||||
search_message = Msg.objects.search_message
|
||||
|
||||
def search_message(*args, **kwargs):
|
||||
"""
|
||||
Search for messages in the database.
|
||||
|
||||
Args:
|
||||
sender (Object, Account or str): Filter by messages sent by this entity.
|
||||
If a string, this is an external sender name.
|
||||
receiver (Object, Account or str): Filter by messages received by this entity.
|
||||
If a string, this is an external receiver name.
|
||||
channel (Channel): Filter by messages sent to this channel.
|
||||
date (datetime): Filter by messages sent on this date.
|
||||
type (str): Filter by messages of this type.
|
||||
tags (str or list): Filter by messages having one or more Tags.
|
||||
This can be a single tag key, a list of tag keys, or a list of
|
||||
tuples (tag_key, tag_category).
|
||||
exclude_tags (str or list): Exclude messages with these tags.
|
||||
search_text (str): Search for text in message content.
|
||||
exact (bool): If True, require exact text match. Default False.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Messages matching the search criteria.
|
||||
"""
|
||||
return Msg.objects.search_message(*args, **kwargs)
|
||||
|
||||
|
||||
search_messages = search_message
|
||||
message_search = search_message
|
||||
messages = search_messages
|
||||
|
||||
#
|
||||
# Search for Communication Channels
|
||||
#
|
||||
# channel_search(self, ostring)
|
||||
#
|
||||
# Search the channel database for a particular channel.
|
||||
#
|
||||
# ostring - the key or database id of the channel.
|
||||
# exact - requires an exact ostring match (not case sensitive)
|
||||
#
|
||||
|
||||
search_channel = ChannelDB.objects.search_channel
|
||||
def search_channel(*args, **kwargs):
|
||||
"""
|
||||
Search for channels in the database.
|
||||
|
||||
Args:
|
||||
key (str or int): Channel key or dbref to search for. This can also
|
||||
be a list of keys/dbrefs. `None` (default) returns all channels.
|
||||
exact (bool): Only valid for string keys. If True, requires exact
|
||||
key match, otherwise also match key with case-insensitive and
|
||||
partial matching. Default is True.
|
||||
candidates (list): Only search among these channel candidates,
|
||||
if given. Default is to search all channels.
|
||||
attribute_name (str): If set, search by channels with this attribute_name
|
||||
defined on them, with the value specified by `attribute_value`.
|
||||
attribute_value (any): What value the given attribute_name must have.
|
||||
typeclass (str or TypeClass): Filter by channels having this typeclass.
|
||||
This can also be a list of typeclasses.
|
||||
tags (str or list): Filter by channels having one or more Tags.
|
||||
This can be a single tag key, a list of tag keys, or a list of
|
||||
tuples (tag_key, tag_category).
|
||||
nofetch (bool): Don't fetch typeclass and perms data from db.
|
||||
This is faster but gives less info.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Channels matching the search criteria.
|
||||
"""
|
||||
return ChannelDB.objects.search_channel(*args, **kwargs)
|
||||
|
||||
|
||||
search_channels = search_channel
|
||||
channel_search = search_channel
|
||||
channels = search_channels
|
||||
|
||||
#
|
||||
# Find help entry objects.
|
||||
#
|
||||
# search_help(self, ostring, help_category=None)
|
||||
#
|
||||
# Retrieve a search entry object.
|
||||
#
|
||||
# ostring - the help topic to look for
|
||||
# category - limit the search to a particular help topic
|
||||
#
|
||||
|
||||
search_help = HelpEntry.objects.search_help
|
||||
def search_help(*args, **kwargs):
|
||||
"""
|
||||
Search for help entries in the database.
|
||||
|
||||
Args:
|
||||
key (str or int): Help entry key or dbref to search for. This can also
|
||||
be a list of keys/dbrefs. `None` (default) returns all help entries.
|
||||
exact (bool): Only valid for string keys. If True, requires exact
|
||||
key match, otherwise also match key with case-insensitive and
|
||||
partial matching. Default is True.
|
||||
category (str): Filter by help entries in this category.
|
||||
tags (str or list): Filter by help entries having one or more Tags.
|
||||
This can be a single tag key, a list of tag keys, or a list of
|
||||
tuples (tag_key, tag_category).
|
||||
locks (str): Filter by help entries with these locks.
|
||||
|
||||
Returns:
|
||||
matches (list): List of HelpEntries matching the search criteria.
|
||||
"""
|
||||
return HelpEntry.objects.search_help(*args, **kwargs)
|
||||
|
||||
|
||||
search_help_entry = search_help
|
||||
search_help_entries = search_help
|
||||
help_entry_search = search_help
|
||||
help_entries = search_help
|
||||
|
||||
|
||||
# Locate Attributes
|
||||
|
||||
# search_object_attribute(key, category, value, strvalue) (also search_attribute works)
|
||||
# search_account_attribute(key, category, value, strvalue) (also search_attribute works)
|
||||
# search_script_attribute(key, category, value, strvalue) (also search_attribute works)
|
||||
# search_channel_attribute(key, category, value, strvalue) (also search_attribute works)
|
||||
|
||||
# Note that these return the object attached to the Attribute,
|
||||
# not the attribute object itself (this is usually what you want)
|
||||
|
||||
|
||||
def search_object_attribute(
|
||||
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Search for objects by their attributes.
|
||||
"""
|
||||
return ObjectDB.objects.get_by_attribute(
|
||||
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
||||
)
|
||||
|
|
@ -227,6 +308,9 @@ def search_object_attribute(
|
|||
def search_account_attribute(
|
||||
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Search for accounts by their attributes.
|
||||
"""
|
||||
return AccountDB.objects.get_by_attribute(
|
||||
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
||||
)
|
||||
|
|
@ -235,6 +319,9 @@ def search_account_attribute(
|
|||
def search_script_attribute(
|
||||
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Search for scripts by their attributes.
|
||||
"""
|
||||
return ScriptDB.objects.get_by_attribute(
|
||||
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
||||
)
|
||||
|
|
@ -243,23 +330,20 @@ def search_script_attribute(
|
|||
def search_channel_attribute(
|
||||
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Search for channels by their attributes.
|
||||
"""
|
||||
return ChannelDB.objects.get_by_attribute(
|
||||
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
||||
)
|
||||
|
||||
|
||||
# search for attribute objects
|
||||
search_attribute_object = ObjectDB.objects.get_attribute
|
||||
|
||||
# Locate Tags
|
||||
|
||||
# search_object_tag(key=None, category=None) (also search_tag works)
|
||||
# search_account_tag(key=None, category=None)
|
||||
# search_script_tag(key=None, category=None)
|
||||
# search_channel_tag(key=None, category=None)
|
||||
|
||||
# Note that this returns the object attached to the tag, not the tag
|
||||
# object itself (this is usually what you want)
|
||||
# Replace direct assignments with functions
|
||||
def search_attribute_object(*args, **kwargs):
|
||||
"""
|
||||
Search for attribute objects.
|
||||
"""
|
||||
return ObjectDB.objects.get_attribute(*args, **kwargs)
|
||||
|
||||
|
||||
def search_object_by_tag(key=None, category=None, tagtype=None, **kwargs):
|
||||
|
|
@ -281,7 +365,6 @@ def search_object_by_tag(key=None, category=None, tagtype=None, **kwargs):
|
|||
matches (list): List of Objects with tags matching
|
||||
the search criteria, or an empty list if no
|
||||
matches were found.
|
||||
|
||||
"""
|
||||
return ObjectDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
|
||||
|
||||
|
|
@ -292,23 +375,6 @@ search_tag = search_object_by_tag # this is the most common case
|
|||
def search_account_tag(key=None, category=None, tagtype=None, **kwargs):
|
||||
"""
|
||||
Find account based on tag or category.
|
||||
|
||||
Args:
|
||||
key (str, optional): The tag key to search for.
|
||||
category (str, optional): The category of tag
|
||||
to search for. If not set, uncategorized
|
||||
tags will be searched.
|
||||
tagtype (str, optional): 'type' of Tag, by default
|
||||
this is either `None` (a normal Tag), `alias` or
|
||||
`permission`. This always apply to all queried tags.
|
||||
kwargs (any): Other optional parameter that may be supported
|
||||
by the manager method.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Accounts with tags matching
|
||||
the search criteria, or an empty list if no
|
||||
matches were found.
|
||||
|
||||
"""
|
||||
return AccountDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
|
||||
|
||||
|
|
@ -316,23 +382,6 @@ def search_account_tag(key=None, category=None, tagtype=None, **kwargs):
|
|||
def search_script_tag(key=None, category=None, tagtype=None, **kwargs):
|
||||
"""
|
||||
Find script based on tag or category.
|
||||
|
||||
Args:
|
||||
key (str, optional): The tag key to search for.
|
||||
category (str, optional): The category of tag
|
||||
to search for. If not set, uncategorized
|
||||
tags will be searched.
|
||||
tagtype (str, optional): 'type' of Tag, by default
|
||||
this is either `None` (a normal Tag), `alias` or
|
||||
`permission`. This always apply to all queried tags.
|
||||
kwargs (any): Other optional parameter that may be supported
|
||||
by the manager method.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Scripts with tags matching
|
||||
the search criteria, or an empty list if no
|
||||
matches were found.
|
||||
|
||||
"""
|
||||
return ScriptDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
|
||||
|
||||
|
|
@ -340,35 +389,16 @@ def search_script_tag(key=None, category=None, tagtype=None, **kwargs):
|
|||
def search_channel_tag(key=None, category=None, tagtype=None, **kwargs):
|
||||
"""
|
||||
Find channel based on tag or category.
|
||||
|
||||
Args:
|
||||
key (str, optional): The tag key to search for.
|
||||
category (str, optional): The category of tag
|
||||
to search for. If not set, uncategorized
|
||||
tags will be searched.
|
||||
tagtype (str, optional): 'type' of Tag, by default
|
||||
this is either `None` (a normal Tag), `alias` or
|
||||
`permission`. This always apply to all queried tags.
|
||||
kwargs (any): Other optional parameter that may be supported
|
||||
by the manager method.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Channels with tags matching
|
||||
the search criteria, or an empty list if no
|
||||
matches were found.
|
||||
|
||||
"""
|
||||
return ChannelDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
|
||||
|
||||
|
||||
# search for tag objects (not the objects they are attached to
|
||||
search_tag_object = ObjectDB.objects.get_tag
|
||||
|
||||
|
||||
# Locate Objects by Typeclass
|
||||
|
||||
# search_objects_by_typeclass(typeclass="", include_children=False, include_parents=False) (also search_typeclass works)
|
||||
# This returns the objects of the given typeclass
|
||||
# Replace direct assignment with function
|
||||
def search_tag_object(*args, **kwargs):
|
||||
"""
|
||||
Search for tag objects.
|
||||
"""
|
||||
return ObjectDB.objects.get_tag(*args, **kwargs)
|
||||
|
||||
|
||||
def search_objects_by_typeclass(typeclass, include_children=False, include_parents=False):
|
||||
|
|
|
|||
|
|
@ -5,86 +5,98 @@ folder and edit it to add/remove links to the menu.
|
|||
{% endcomment %}
|
||||
{% load static %}
|
||||
<nav class="navbar navbar-dark font-weight-bold navbar-expand-md">
|
||||
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#menu-content" aria-controls="menu-content" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#menu-content"
|
||||
aria-controls="menu-content" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<a class="navbar-brand" href="/">
|
||||
<div class="media">
|
||||
<img class="d-flex navbar-brand-logo mx-3" src="{% static "website/images/evennia_logo.png" %}" alt="{{game_name}} logo" />
|
||||
<div class="media-body">
|
||||
{{ game_name }}<br />
|
||||
<small>{{game_slogan}}</small>
|
||||
</div>
|
||||
<a class="navbar-brand" href="/">
|
||||
<div class="media">
|
||||
<img class="d-flex navbar-brand-logo mx-3" src="{% static "website/images/evennia_logo.png" %}"
|
||||
alt="{{game_name}} logo" />
|
||||
<div class="media-body">
|
||||
{{ game_name }}<br />
|
||||
<small>{{game_slogan}}</small>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="collapse navbar-collapse" id="menu-content">
|
||||
<ul class="navbar-nav">
|
||||
{% block navbar_left %}
|
||||
<li>
|
||||
<a class="nav-link" href="{% url 'index' %}">Home</a>
|
||||
</li>
|
||||
<!-- game views -->
|
||||
<li><a class="nav-link" href="{% url 'characters' %}">Characters</a></li>
|
||||
<li><a class="nav-link" href="{% url 'channels' %}">Channels</a></li>
|
||||
<li><a class="nav-link" href="{% url 'help' %}">Help</a></li>
|
||||
<!-- end game views -->
|
||||
|
||||
{% if webclient_enabled %}
|
||||
<li><a class="nav-link" href="{% url 'webclient:index' %}">Play Online</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_staff %}
|
||||
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
{% if rest_api_enabled %}
|
||||
<li><a class="nav-link" href="/api">API</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
<ul class="nav navbar-nav ml-auto w-120 justify-content-end">
|
||||
{% block navbar_right %}
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar_user %}
|
||||
{% if account %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" id="user_options" aria-expanded="false">
|
||||
{% if puppet %}
|
||||
Welcome, {{ puppet }}! <span class="text-muted">({{ account.username }})</span> <span class="caret"></span>
|
||||
{% else %}
|
||||
Logged in as {{ account.username }} <span class="caret"></span>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="user_options">
|
||||
<a class="dropdown-item" href="{% url 'character-create' %}">Create Character</a>
|
||||
<a class="dropdown-item" href="{% url 'character-manage' %}">Manage Characters</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
{% for character in account.characters|slice:"10" %}
|
||||
<a class="dropdown-item" href="{{ character.web_get_puppet_url }}?next={{ request.path }}">{{ character }}</a>
|
||||
{% empty %}
|
||||
<a class="dropdown-item" href="#">No characters found!</a>
|
||||
{% endfor %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'password_change' %}">Change Password</a>
|
||||
<a class="dropdown-item" href="{% url 'logout' %}">Log Out</a>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link" href="{% url 'logout' %}">Log Out</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<a class="nav-link" href="{% url 'login' %}?next={{ request.path }}">Log In</a>
|
||||
</li>
|
||||
{% if register_enabled %}
|
||||
<li>
|
||||
<a class="nav-link" href="{% url 'register' %}">Register</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="collapse navbar-collapse" id="menu-content">
|
||||
<ul class="navbar-nav">
|
||||
{% block navbar_left %}
|
||||
<li>
|
||||
<a class="nav-link" href="{% url 'index' %}">Home</a>
|
||||
</li>
|
||||
<!-- game views -->
|
||||
<li><a class="nav-link" href="{% url 'characters' %}">Characters</a></li>
|
||||
<li><a class="nav-link" href="{% url 'channels' %}">Channels</a></li>
|
||||
<li><a class="nav-link" href="{% url 'help' %}">Help</a></li>
|
||||
<!-- end game views -->
|
||||
|
||||
{% if webclient_enabled %}
|
||||
<li><a class="nav-link" href="{% url 'webclient:index' %}">Play Online</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_staff %}
|
||||
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
{% if rest_api_enabled %}
|
||||
<li><a class="nav-link" href="/api">API</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
<ul class="nav navbar-nav ml-auto w-120 justify-content-end">
|
||||
{% block navbar_right %}
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar_user %}
|
||||
{% if account %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" id="user_options" aria-expanded="false">
|
||||
{% if puppet %}
|
||||
Welcome, {{ puppet }}! <span class="text-muted">({{ account.username }})</span> <span class="caret"></span>
|
||||
{% else %}
|
||||
Logged in as {{ account.username }} <span class="caret"></span>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="user_options">
|
||||
<a class="dropdown-item" href="{% url 'character-create' %}">Create Character</a>
|
||||
<a class="dropdown-item" href="{% url 'character-manage' %}">Manage Characters</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
{% for character in account.characters|slice:"10" %}
|
||||
<a class="dropdown-item" href="{{ character.web_get_puppet_url }}?next={{ request.path }}">{{ character }}</a>
|
||||
{% empty %}
|
||||
<a class="dropdown-item" href="#">No characters found!</a>
|
||||
{% endfor %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'password_change' %}">Change Password</a>
|
||||
<form method="post" action="{% url 'logout' %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="dropdown-item">Log Out</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<form method="post" action="{% url 'logout' %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="nav-link btn btn-link">Log Out</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<form method="post" action="{% url 'login' %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ request.path }}">
|
||||
<button type="submit" class="nav-link btn btn-link">Log In</button>
|
||||
</form>
|
||||
</li>
|
||||
{% if register_enabled %}
|
||||
<li>
|
||||
<a class="nav-link" href="{% url 'register' %}">Register</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -97,6 +97,20 @@ class LoginTest(EvenniaWebTest):
|
|||
class LogoutTest(EvenniaWebTest):
|
||||
url_name = "logout"
|
||||
|
||||
def test_get(self):
|
||||
"""Since Django 5.0, logout is no longer supported with GET requests"""
|
||||
pass
|
||||
|
||||
def test_post(self):
|
||||
"""Do the logout test with a POST request"""
|
||||
response = self.client.post(reverse(self.url_name), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_authenticated(self):
|
||||
"""Do the logout test with a POST instead of GET"""
|
||||
response = self.client.post(reverse(self.url_name), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class PasswordResetTest(EvenniaWebTest):
|
||||
url_name = "password_change"
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ classifiers = [
|
|||
dependencies = [
|
||||
# core dependencies
|
||||
"legacy-cgi;python_version >= '3.13'",
|
||||
"django >= 4.2, < 4.3",
|
||||
"django >= 5.1, < 5.2",
|
||||
"twisted >= 24.11.0, < 25",
|
||||
"pytz >= 2022.6",
|
||||
"djangorestframework >= 3.14, < 3.15",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue