Feat: Default object’s default descs are now taken from a default_description
class variable instead of the desc Attribute always being set (count-infinity)
@@ -214,8 +216,7 @@ class variable instead of the None if no name is provided, also enforce string type (InspectorCaracal)
Fix: Allow in-game help searching for commands natively starting
with * (which is the Lunr search wildcard) (count-infinity)
-
Fix: Web client stopped auto-focusing the input box after opening
-settings (count-infinity)
+
Fix: Web client stopped auto-focusing the input box after opening settings (count-infinity)
Fix: Partial matching fix in default search, makes sure e.g. bsw uniquely
finds bigsword even if another type of sword is around (InspectorCaracal)
Fix: In searches, allow special ‘here’ and ‘me’ keywords only be valid queries
diff --git a/docs/latest/_modules/django/conf.html b/docs/latest/_modules/django/conf.html
index d667e5ec36..5492859016 100644
--- a/docs/latest/_modules/django/conf.html
+++ b/docs/latest/_modules/django/conf.html
@@ -107,37 +107,16 @@
importdjangofromdjango.confimportglobal_settingsfromdjango.core.exceptionsimportImproperlyConfigured
-fromdjango.utils.deprecationimportRemovedInDjango50Warning,RemovedInDjango51Warning
+fromdjango.utils.deprecationimportRemovedInDjango60Warningfromdjango.utils.functionalimportLazyObject,emptyENVIRONMENT_VARIABLE="DJANGO_SETTINGS_MODULE"DEFAULT_STORAGE_ALIAS="default"STATICFILES_STORAGE_ALIAS="staticfiles"
-# RemovedInDjango50Warning
-USE_DEPRECATED_PYTZ_DEPRECATED_MSG=(
- "The USE_DEPRECATED_PYTZ setting, and support for pytz timezones is "
- "deprecated in favor of the stdlib zoneinfo module. Please update your "
- "code to use zoneinfo and remove the USE_DEPRECATED_PYTZ setting."
-)
-
-USE_L10N_DEPRECATED_MSG=(
- "The USE_L10N setting is deprecated. Starting with Django 5.0, localized "
- "formatting of data will always be enabled. For example Django will "
- "display numbers and dates using the format of the current locale."
-)
-
-CSRF_COOKIE_MASKED_DEPRECATED_MSG=(
- "The CSRF_COOKIE_MASKED transitional setting is deprecated. Support for "
- "it will be removed in Django 5.0."
-)
-
-DEFAULT_FILE_STORAGE_DEPRECATED_MSG=(
- "The DEFAULT_FILE_STORAGE setting is deprecated. Use STORAGES instead."
-)
-
-STATICFILES_STORAGE_DEPRECATED_MSG=(
- "The STATICFILES_STORAGE setting is deprecated. Use STORAGES instead."
+# RemovedInDjango60Warning.
+FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG=(
+ "The FORMS_URLFIELD_ASSUME_HTTPS transitional setting is deprecated.")
@@ -264,36 +243,6 @@
ifnotfilename.startswith(os.path.dirname(django.__file__)):warnings.warn(message,category,stacklevel=2)
- @property
- defUSE_L10N(self):
- self._show_deprecation_warning(
- USE_L10N_DEPRECATED_MSG,RemovedInDjango50Warning
- )
- returnself.__getattr__("USE_L10N")
-
- # RemovedInDjango50Warning.
- @property
- def_USE_L10N_INTERNAL(self):
- # Special hook to avoid checking a traceback in internal use on hot
- # paths.
- returnself.__getattr__("USE_L10N")
-
- # RemovedInDjango51Warning.
- @property
- defDEFAULT_FILE_STORAGE(self):
- self._show_deprecation_warning(
- DEFAULT_FILE_STORAGE_DEPRECATED_MSG,RemovedInDjango51Warning
- )
- returnself.__getattr__("DEFAULT_FILE_STORAGE")
-
- # RemovedInDjango51Warning.
- @property
- defSTATICFILES_STORAGE(self):
- self._show_deprecation_warning(
- STATICFILES_STORAGE_DEPRECATED_MSG,RemovedInDjango51Warning
- )
- returnself.__getattr__("STATICFILES_STORAGE")
-
classSettings:def__init__(self,settings_module):
@@ -328,20 +277,12 @@
setattr(self,setting,setting_value)self._explicit_settings.add(setting)
- ifself.USE_TZisFalseandnotself.is_overridden("USE_TZ"):
+ ifself.is_overridden("FORMS_URLFIELD_ASSUME_HTTPS"):warnings.warn(
- "The default value of USE_TZ will change from False to True "
- "in Django 5.0. Set USE_TZ to False in your project settings "
- "if you want to keep the current default behavior.",
- category=RemovedInDjango50Warning,
+ FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG,
+ RemovedInDjango60Warning,)
- ifself.is_overridden("USE_DEPRECATED_PYTZ"):
- warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG,RemovedInDjango50Warning)
-
- ifself.is_overridden("CSRF_COOKIE_MASKED"):
- warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG,RemovedInDjango50Warning)
-
ifhasattr(time,"tzset")andself.TIME_ZONE:# When we can, attempt to validate the timezone. If we can't find# this file, no check happens and it's harmless.
@@ -354,43 +295,6 @@
os.environ["TZ"]=self.TIME_ZONEtime.tzset()
- ifself.is_overridden("USE_L10N"):
- warnings.warn(USE_L10N_DEPRECATED_MSG,RemovedInDjango50Warning)
-
- ifself.is_overridden("DEFAULT_FILE_STORAGE"):
- ifself.is_overridden("STORAGES"):
- raiseImproperlyConfigured(
- "DEFAULT_FILE_STORAGE/STORAGES are mutually exclusive."
- )
- self.STORAGES={
- **self.STORAGES,
- DEFAULT_STORAGE_ALIAS:{"BACKEND":self.DEFAULT_FILE_STORAGE},
- }
- warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG,RemovedInDjango51Warning)
-
- ifself.is_overridden("STATICFILES_STORAGE"):
- ifself.is_overridden("STORAGES"):
- raiseImproperlyConfigured(
- "STATICFILES_STORAGE/STORAGES are mutually exclusive."
- )
- self.STORAGES={
- **self.STORAGES,
- STATICFILES_STORAGE_ALIAS:{"BACKEND":self.STATICFILES_STORAGE},
- }
- warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG,RemovedInDjango51Warning)
- # RemovedInDjango51Warning.
- ifself.is_overridden("STORAGES"):
- setattr(
- self,
- "DEFAULT_FILE_STORAGE",
- self.STORAGES.get(DEFAULT_STORAGE_ALIAS,{}).get("BACKEND"),
- )
- setattr(
- self,
- "STATICFILES_STORAGE",
- self.STORAGES.get(STATICFILES_STORAGE_ALIAS,{}).get("BACKEND"),
- )
-
defis_overridden(self,setting):returnsettinginself._explicit_settings
@@ -423,47 +327,12 @@
def__setattr__(self,name,value):self._deleted.discard(name)
- ifname=="USE_L10N":
- warnings.warn(USE_L10N_DEPRECATED_MSG,RemovedInDjango50Warning)
- ifname=="CSRF_COOKIE_MASKED":
- warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG,RemovedInDjango50Warning)
- ifname=="DEFAULT_FILE_STORAGE":
- self.STORAGES[DEFAULT_STORAGE_ALIAS]={
- "BACKEND":self.DEFAULT_FILE_STORAGE
- }
- warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG,RemovedInDjango51Warning)
- ifname=="STATICFILES_STORAGE":
- self.STORAGES[STATICFILES_STORAGE_ALIAS]={
- "BACKEND":self.STATICFILES_STORAGE
- }
- warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG,RemovedInDjango51Warning)
+ ifname=="FORMS_URLFIELD_ASSUME_HTTPS":
+ warnings.warn(
+ FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG,
+ RemovedInDjango60Warning,
+ )super().__setattr__(name,value)
- ifname=="USE_DEPRECATED_PYTZ":
- warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG,RemovedInDjango50Warning)
- # RemovedInDjango51Warning.
- ifname=="STORAGES":
- ifdefault_file_storage:=self.STORAGES.get(DEFAULT_STORAGE_ALIAS):
- super().__setattr__(
- "DEFAULT_FILE_STORAGE",default_file_storage.get("BACKEND")
- )
- else:
- self.STORAGES.setdefault(
- DEFAULT_STORAGE_ALIAS,
- {"BACKEND":"django.core.files.storage.FileSystemStorage"},
- )
- ifstaticfiles_storage:=self.STORAGES.get(STATICFILES_STORAGE_ALIAS):
- super().__setattr__(
- "STATICFILES_STORAGE",staticfiles_storage.get("BACKEND")
- )
- else:
- self.STORAGES.setdefault(
- STATICFILES_STORAGE_ALIAS,
- {
- "BACKEND":(
- "django.contrib.staticfiles.storage.StaticFilesStorage"
- ),
- },
- )def__delattr__(self,name):self._deleted.add(name)
diff --git a/docs/latest/_modules/django/db/models/fields/related_descriptors.html b/docs/latest/_modules/django/db/models/fields/related_descriptors.html
index 3a905fd356..7d6668ec39 100644
--- a/docs/latest/_modules/django/db/models/fields/related_descriptors.html
+++ b/docs/latest/_modules/django/db/models/fields/related_descriptors.html
@@ -154,6 +154,8 @@
``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead."""
+importwarnings
+
fromasgiref.syncimportsync_to_asyncfromdjango.core.exceptionsimportFieldError
@@ -164,12 +166,13 @@
router,transaction,)
-fromdjango.db.modelsimportQ,Window,signals
+fromdjango.db.modelsimportManager,Q,Window,signalsfromdjango.db.models.functionsimportRowNumberfromdjango.db.models.lookupsimportGreaterThan,LessThanOrEqualfromdjango.db.models.queryimportQuerySetfromdjango.db.models.query_utilsimportDeferredAttributefromdjango.db.models.utilsimportAltersData,resolve_callables
+fromdjango.utils.deprecationimportRemovedInDjango60Warningfromdjango.utils.functionalimportcached_property
@@ -244,8 +247,23 @@
returnself.field.remote_field.model._base_manager.db_manager(hints=hints).all()defget_prefetch_queryset(self,instances,queryset=None):
+ warnings.warn(
+ "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
+ "instead.",
+ RemovedInDjango60Warning,
+ stacklevel=2,
+ )ifquerysetisNone:
- queryset=self.get_queryset()
+ returnself.get_prefetch_querysets(instances)
+ returnself.get_prefetch_querysets(instances,[queryset])
+
+ defget_prefetch_querysets(self,instances,querysets=None):
+ ifquerysetsandlen(querysets)!=1:
+ raiseValueError(
+ "querysets argument of get_prefetch_querysets() should have a length "
+ "of 1."
+ )
+ queryset=querysets[0]ifquerysetselseself.get_queryset()queryset._add_hints(instance=instances[0])rel_obj_attr=self.field.get_foreign_related_value
@@ -260,7 +278,7 @@
# (related_name ends with a '+'). Refs #21410.# The check for len(...) == 1 is a special case that allows the query# to be join-less and smaller. Refs #21760.
- ifremote_field.is_hidden()orlen(self.field.foreign_related_fields)==1:
+ ifremote_field.hiddenorlen(self.field.foreign_related_fields)==1:query={"%s__in"%related_field.name:{instance_attr(inst)[0]forinstininstances}
@@ -268,6 +286,9 @@
else:query={"%s__in"%self.field.related_query_name():instances}queryset=queryset.filter(**query)
+ # There can be only one object prefetched for each instance so clear
+ # ordering if the query allows it without side effects.
+ queryset.query.clear_ordering()# Since we're going to assign directly in the cache,# we must manage the reverse relation cache manually.
@@ -280,7 +301,7 @@
rel_obj_attr,instance_attr,True,
- self.field.get_cache_name(),
+ self.field.cache_name,False,)
@@ -518,8 +539,23 @@
returnself.related.related_model._base_manager.db_manager(hints=hints).all()defget_prefetch_queryset(self,instances,queryset=None):
+ warnings.warn(
+ "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
+ "instead.",
+ RemovedInDjango60Warning,
+ stacklevel=2,
+ )ifquerysetisNone:
- queryset=self.get_queryset()
+ returnself.get_prefetch_querysets(instances)
+ returnself.get_prefetch_querysets(instances,[queryset])
+
+ defget_prefetch_querysets(self,instances,querysets=None):
+ ifquerysetsandlen(querysets)!=1:
+ raiseValueError(
+ "querysets argument of get_prefetch_querysets() should have a length "
+ "of 1."
+ )
+ queryset=querysets[0]ifquerysetselseself.get_queryset()queryset._add_hints(instance=instances[0])rel_obj_attr=self.related.field.get_local_related_value
@@ -527,6 +563,9 @@
instances_dict={instance_attr(inst):instforinstininstances}query={"%s__in"%self.related.field.name:instances}queryset=queryset.filter(**query)
+ # There can be only one object prefetched for each instance so clear
+ # ordering if the query allows it without side effects.
+ queryset.query.clear_ordering()# Since we're going to assign directly in the cache,# we must manage the reverse relation cache manually.
@@ -538,7 +577,7 @@
rel_obj_attr,instance_attr,True,
- self.related.get_cache_name(),
+ self.related.cache_name,False,)
@@ -582,7 +621,7 @@
ifrel_objisNone:raiseself.RelatedObjectDoesNotExist("%s has no %s."
- %(instance.__class__.__name__,self.related.get_accessor_name())
+ %(instance.__class__.__name__,self.related.accessor_name))else:returnrel_obj
@@ -622,7 +661,7 @@
%(value,instance._meta.object_name,
- self.related.get_accessor_name(),
+ self.related.accessor_name,self.related.related_model._meta.object_name,))
@@ -710,7 +749,7 @@
def_get_set_deprecation_msg_params(self):return("reverse side of a related set",
- self.rel.get_accessor_name(),
+ self.rel.accessor_name,)def__set__(self,instance,value):
@@ -796,7 +835,7 @@
def_remove_prefetched_objects(self):try:self.instance._prefetched_objects_cache.pop(
- self.field.remote_field.get_cache_name()
+ self.field.remote_field.cache_name)except(AttributeError,KeyError):pass# nothing to clear from cache
@@ -812,16 +851,30 @@
)try:returnself.instance._prefetched_objects_cache[
- self.field.remote_field.get_cache_name()
+ self.field.remote_field.cache_name]except(AttributeError,KeyError):queryset=super().get_queryset()returnself._apply_rel_filters(queryset)defget_prefetch_queryset(self,instances,queryset=None):
+ warnings.warn(
+ "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
+ "instead.",
+ RemovedInDjango60Warning,
+ stacklevel=2,
+ )ifquerysetisNone:
- queryset=super().get_queryset()
+ returnself.get_prefetch_querysets(instances)
+ returnself.get_prefetch_querysets(instances,[queryset])
+ defget_prefetch_querysets(self,instances,querysets=None):
+ ifquerysetsandlen(querysets)!=1:
+ raiseValueError(
+ "querysets argument of get_prefetch_querysets() should have a "
+ "length of 1."
+ )
+ queryset=querysets[0]ifquerysetselsesuper().get_queryset()queryset._add_hints(instance=instances[0])queryset=queryset.using(queryset._dborself._db)
@@ -836,7 +889,7 @@
ifnotself.field.is_cached(rel_obj):instance=instances_dict[rel_obj_attr(rel_obj)]setattr(rel_obj,self.field.name,instance)
- cache_name=self.field.remote_field.get_cache_name()
+ cache_name=self.field.remote_field.cache_namereturnqueryset,rel_obj_attr,instance_attr,False,cache_name,Falsedefadd(self,*objs,bulk=True):
@@ -885,6 +938,7 @@
defcreate(self,**kwargs):self._check_fk_val()
+ self._remove_prefetched_objects()kwargs[self.field.name]=self.instancedb=router.db_for_write(self.model,instance=self.instance)returnsuper(RelatedManager,self.db_manager(db)).create(**kwargs)
@@ -1062,7 +1116,7 @@
return("%s side of a many-to-many set"%("reverse"ifself.reverseelse"forward"),
- self.rel.get_accessor_name()ifself.reverseelseself.field.name,
+ self.rel.accessor_nameifself.reverseelseself.field.name,)
@@ -1164,6 +1218,12 @@
queryset._defer_next_filter=Truereturnqueryset._next_is_sticky().filter(**self.core_filters)
+ defget_prefetch_cache(self):
+ try:
+ returnself.instance._prefetched_objects_cache[self.prefetch_cache_name]
+ except(AttributeError,KeyError):
+ returnNone
+
def_remove_prefetched_objects(self):try:self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
@@ -1171,16 +1231,30 @@
pass# nothing to clear from cachedefget_queryset(self):
- try:
- returnself.instance._prefetched_objects_cache[self.prefetch_cache_name]
- except(AttributeError,KeyError):
+ if(cache:=self.get_prefetch_cache())isnotNone:
+ returncache
+ else:queryset=super().get_queryset()returnself._apply_rel_filters(queryset)defget_prefetch_queryset(self,instances,queryset=None):
+ warnings.warn(
+ "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
+ "instead.",
+ RemovedInDjango60Warning,
+ stacklevel=2,
+ )ifquerysetisNone:
- queryset=super().get_queryset()
+ returnself.get_prefetch_querysets(instances)
+ returnself.get_prefetch_querysets(instances,[queryset])
+ defget_prefetch_querysets(self,instances,querysets=None):
+ ifquerysetsandlen(querysets)!=1:
+ raiseValueError(
+ "querysets argument of get_prefetch_querysets() should have a "
+ "length of 1."
+ )
+ queryset=querysets[0]ifquerysetselsesuper().get_queryset()queryset._add_hints(instance=instances[0])queryset=queryset.using(queryset._dborself._db)queryset=_filter_prefetch_queryset(
@@ -1209,7 +1283,10 @@
return(queryset,lambdaresult:tuple(
- getattr(result,"_prefetch_related_val_%s"%f.attname)
+ f.get_db_prep_value(
+ getattr(result,f"_prefetch_related_val_{f.attname}"),
+ connection,
+ )forfinfk.local_related_fields),lambdainst:tuple(
@@ -1221,6 +1298,45 @@
False,)
+ @property
+ defconstrained_target(self):
+ # If the through relation's target field's foreign integrity is
+ # enforced, the query can be performed solely against the through
+ # table as the INNER JOIN'ing against target table is unnecessary.
+ ifnotself.target_field.db_constraint:
+ returnNone
+ db=router.db_for_read(self.through,instance=self.instance)
+ ifnotconnections[db].features.supports_foreign_keys:
+ returnNone
+ hints={"instance":self.instance}
+ manager=self.through._base_manager.db_manager(db,hints=hints)
+ filters={self.source_field_name:self.instance.pk}
+ # Nullable target rows must be excluded as well as they would have
+ # been filtered out from an INNER JOIN.
+ ifself.target_field.null:
+ filters["%s__isnull"%self.target_field_name]=False
+ returnmanager.filter(**filters)
+
+ defexists(self):
+ if(
+ superclassisManager
+ andself.get_prefetch_cache()isNone
+ and(constrained_target:=self.constrained_target)isnotNone
+ ):
+ returnconstrained_target.exists()
+ else:
+ returnsuper().exists()
+
+ defcount(self):
+ if(
+ superclassisManager
+ andself.get_prefetch_cache()isNone
+ and(constrained_target:=self.constrained_target)isnotNone
+ ):
+ returnconstrained_target.count()
+ else:
+ returnsuper().count()
+
defadd(self,*objs,through_defaults=None):self._remove_prefetched_objects()db=router.db_for_write(self.through,instance=self.instance)
diff --git a/docs/latest/_modules/django/db/models/query.html b/docs/latest/_modules/django/db/models/query.html
index cb39f915e3..d465898e21 100644
--- a/docs/latest/_modules/django/db/models/query.html
+++ b/docs/latest/_modules/django/db/models/query.html
@@ -124,7 +124,7 @@
resolve_callables,)fromdjango.utilsimporttimezone
-fromdjango.utils.deprecationimportRemovedInDjango50Warning
+fromdjango.utils.deprecationimportRemovedInDjango60Warningfromdjango.utils.functionalimportcached_property,partition# The maximum number of results to fetch in a get() query.
@@ -200,9 +200,11 @@
related_objs,operator.attrgetter(*[
- field.attname
- iffrom_field=="self"
- elsequeryset.model._meta.get_field(from_field).attname
+ (
+ field.attname
+ iffrom_field=="self"
+ elsequeryset.model._meta.get_field(from_field).attname
+ )forfrom_fieldinfield.from_fields]),
@@ -620,16 +622,9 @@
"""ifchunk_sizeisNone:ifself._prefetch_related_lookups:
- # When the deprecation ends, replace with:
- # raise ValueError(
- # 'chunk_size must be provided when using '
- # 'QuerySet.iterator() after prefetch_related().'
- # )
- warnings.warn(
- "Using QuerySet.iterator() after prefetch_related() "
- "without specifying chunk_size is deprecated.",
- category=RemovedInDjango50Warning,
- stacklevel=2,
+ raiseValueError(
+ "chunk_size must be provided when using QuerySet.iterator() after "
+ "prefetch_related().")elifchunk_size<=0:raiseValueError("Chunk size must be strictly positive.")
@@ -643,19 +638,36 @@
An asynchronous iterator over the results from applying this QuerySet to the database. """
- ifself._prefetch_related_lookups:
- raiseNotSupportedError(
- "Using QuerySet.aiterator() after prefetch_related() is not supported."
- )ifchunk_size<=0:raiseValueError("Chunk size must be strictly positive.")use_chunked_fetch=notconnections[self.db].settings_dict.get("DISABLE_SERVER_SIDE_CURSORS")
- asyncforiteminself._iterable_class(
+ iterable=self._iterable_class(self,chunked_fetch=use_chunked_fetch,chunk_size=chunk_size
- ):
- yielditem
+ )
+ ifself._prefetch_related_lookups:
+ results=[]
+
+ asyncforiteminiterable:
+ results.append(item)
+ iflen(results)>=chunk_size:
+ awaitaprefetch_related_objects(
+ results,*self._prefetch_related_lookups
+ )
+ forresultinresults:
+ yieldresult
+ results.clear()
+
+ ifresults:
+ awaitaprefetch_related_objects(
+ results,*self._prefetch_related_lookups
+ )
+ forresultinresults:
+ yieldresult
+ else:
+ asyncforiteminiterable:
+ yielditemdefaggregate(self,*args,**kwargs):"""
@@ -744,6 +756,15 @@
Create a new object with the given kwargs, saving it to the database and returning the created object. """
+ reverse_one_to_one_fields=frozenset(kwargs).intersection(
+ self.model._meta._reverse_one_to_one_field_names
+ )
+ ifreverse_one_to_one_fields:
+ raiseValueError(
+ "The following fields do not exist in this model: %s"
+ %", ".join(reverse_one_to_one_fields)
+ )
+
obj=self.model(**kwargs)self._for_write=Trueobj.save(force_insert=True,using=self.db)
@@ -753,10 +774,21 @@
returnawaitsync_to_async(self.create)(**kwargs)def_prepare_for_bulk_create(self,objs):
+ fromdjango.db.models.expressionsimportDatabaseDefault
+
+ connection=connections[self.db]forobjinobjs:ifobj.pkisNone:# Populate new PK values.obj.pk=obj._meta.pk.get_pk_value_on_save(obj)
+ ifnotconnection.features.supports_default_keyword_in_bulk_insert:
+ forfieldinobj._meta.fields:
+ iffield.generated:
+ continue
+ value=getattr(obj,field.attname)
+ ifisinstance(value,DatabaseDefault):
+ setattr(obj,field.attname,field.db_default)
+
obj._prepare_related_fields_for_save(operation_name="bulk_create")def_check_bulk_create_options(
@@ -847,7 +879,7 @@
# model to detect the inheritance pattern ConcreteGrandParent -># MultiTableParent -> ProxyChild. Simply checking self.model._meta.proxy# would not identify that case as involving multiple tables.
- forparentinself.model._meta.get_parent_list():
+ forparentinself.model._meta.all_parents:ifparent._meta.concrete_modelisnotself.model._meta.concrete_model:raiseValueError("Can't bulk create a multi-table inherited model")ifnotobjs:
@@ -868,7 +900,7 @@
unique_fields,)self._for_write=True
- fields=opts.concrete_fields
+ fields=[fforfinopts.concrete_fieldsifnotf.generated]objs=list(objs)self._prepare_for_bulk_create(objs)withtransaction.atomic(using=self.db,savepoint=False):
@@ -1025,25 +1057,32 @@
**kwargs,)
- defupdate_or_create(self,defaults=None,**kwargs):
+ defupdate_or_create(self,defaults=None,create_defaults=None,**kwargs):""" Look up an object with the given kwargs, updating one with defaults
- if it exists, otherwise create a new one.
+ if it exists, otherwise create a new one. Optionally, an object can
+ be created with different values than defaults by using
+ create_defaults. Return a tuple (object, created), where created is a boolean specifying whether an object was created. """
- defaults=defaultsor{}
+ update_defaults=defaultsor{}
+ ifcreate_defaultsisNone:
+ create_defaults=update_defaults
+
self._for_write=Truewithtransaction.atomic(using=self.db):# Lock the row so that a concurrent update is blocked until# update_or_create() has performed its save.
- obj,created=self.select_for_update().get_or_create(defaults,**kwargs)
+ obj,created=self.select_for_update().get_or_create(
+ create_defaults,**kwargs
+ )ifcreated:returnobj,created
- fork,vinresolve_callables(defaults):
+ fork,vinresolve_callables(update_defaults):setattr(obj,k,v)
- update_fields=set(defaults)
+ update_fields=set(update_defaults)concrete_field_names=self.model._meta._non_pk_concrete_field_names# update_fields does not support non-concrete fields.ifconcrete_field_names.issuperset(update_fields):
@@ -1063,9 +1102,10 @@
obj.save(using=self.db)returnobj,False
- asyncdefaupdate_or_create(self,defaults=None,**kwargs):
+ asyncdefaupdate_or_create(self,defaults=None,create_defaults=None,**kwargs):returnawaitsync_to_async(self.update_or_create)(defaults=defaults,
+ create_defaults=create_defaults,**kwargs,)
@@ -1199,9 +1239,9 @@
qs=()foroffsetinrange(0,len(id_list),batch_size):batch=id_list[offset:offset+batch_size]
- qs+=tuple(self.filter(**{filter_key:batch}).order_by())
+ qs+=tuple(self.filter(**{filter_key:batch}))else:
- qs=self.filter(**{filter_key:id_list}).order_by()
+ qs=self.filter(**{filter_key:id_list})else:qs=self._chain()return{getattr(obj,field_name):objforobjinqs}
@@ -1217,8 +1257,8 @@
self._not_support_combined_queries("delete")ifself.query.is_sliced:raiseTypeError("Cannot use 'limit' or 'offset' with delete().")
- ifself.query.distinctorself.query.distinct_fields:
- raiseTypeError("Cannot call delete() after .distinct().")
+ ifself.query.distinct_fields:
+ raiseTypeError("Cannot call delete() after .distinct(*fields).")ifself._fieldsisnotNone:raiseTypeError("Cannot call delete() after .values() or .values_list()")
@@ -1236,11 +1276,11 @@
collector=Collector(using=del_query.db,origin=self)collector.collect(del_query)
- deleted,_rows_count=collector.delete()
+ num_deleted,num_deleted_per_model=collector.delete()# Clear the result cache, in case this QuerySet gets reused.self._result_cache=None
- returndeleted,_rows_count
+ returnnum_deleted,num_deleted_per_modeldelete.alters_data=Truedelete.queryset_only=True
@@ -1281,11 +1321,18 @@
# Inline annotations in order_by(), if possible.new_order_by=[]forcolinquery.order_by:
- ifannotation:=query.annotations.get(col):
+ alias=col
+ descending=False
+ ifisinstance(alias,str)andalias.startswith("-"):
+ alias=alias.removeprefix("-")
+ descending=True
+ ifannotation:=query.annotations.get(alias):ifgetattr(annotation,"contains_aggregate",False):raiseexceptions.FieldError(f"Cannot update when ordering by an aggregate: {annotation}")
+ ifdescending:
+ annotation=annotation.desc()new_order_by.append(annotation)else:new_order_by.append(col)
@@ -1437,9 +1484,7 @@
clone._iterable_class=(NamedValuesListIterableifnamed
- elseFlatValuesListIterable
- ifflat
- elseValuesListIterable
+ elseFlatValuesListIterableifflatelseValuesListIterable)returnclone
@@ -1463,11 +1508,7 @@
.order_by(("-"iforder=="DESC"else"")+"datefield"))
- # RemovedInDjango50Warning: when the deprecation ends, remove is_dst
- # argument.
- defdatetimes(
- self,field_name,kind,order="ASC",tzinfo=None,is_dst=timezone.NOT_PASSED
- ):
+ defdatetimes(self,field_name,kind,order="ASC",tzinfo=None):""" Return a list of datetime objects representing all available datetimes for the given field_name, scoped to 'kind'.
@@ -1491,7 +1532,6 @@
kind,output_field=DateTimeField(),tzinfo=tzinfo,
- is_dst=is_dst,),plain_field=F(field_name),)
@@ -1710,9 +1750,11 @@
ifnamesisNone:names=set(chain.from_iterable(
- (field.name,field.attname)
- ifhasattr(field,"attname")
- else(field.name,)
+ (
+ (field.name,field.attname)
+ ifhasattr(field,"attname")
+ else(field.name,)
+ )forfieldinself.model._meta.get_fields()))
@@ -1917,12 +1959,17 @@
inserted_rows=[]bulk_return=connection.features.can_return_rows_from_bulk_insertforitemin[objs[i:i+batch_size]foriinrange(0,len(objs),batch_size)]:
- ifbulk_returnandon_conflictisNone:
+ ifbulk_returnand(
+ on_conflictisNoneoron_conflict==OnConflict.UPDATE
+ ):inserted_rows.extend(self._insert(item,fields=fields,using=self.db,
+ on_conflict=on_conflict,
+ update_fields=update_fields,
+ unique_fields=unique_fields,returning_fields=self.model._meta.db_returning_fields,))
@@ -2230,8 +2277,7 @@
converter=connections[self.db].introspection.identifier_convertermodel_fields={}forfieldinself.model._meta.fields:
- name,column=field.get_attname_column()
- model_fields[converter(column)]=field
+ model_fields[converter(field.column)]=fieldreturnmodel_fields
@@ -2283,8 +2329,21 @@
returnto_attr,as_attrdefget_current_queryset(self,level):
- ifself.get_current_prefetch_to(level)==self.prefetch_to:
- returnself.queryset
+ warnings.warn(
+ "Prefetch.get_current_queryset() is deprecated. Use "
+ "get_current_querysets() instead.",
+ RemovedInDjango60Warning,
+ stacklevel=2,
+ )
+ querysets=self.get_current_querysets(level)
+ returnquerysets[0]ifquerysetsisnotNoneelseNone
+
+ defget_current_querysets(self,level):
+ if(
+ self.get_current_prefetch_to(level)==self.prefetch_to
+ andself.querysetisnotNone
+ ):
+ return[self.queryset]returnNonedef__eq__(self,other):
@@ -2462,23 +2521,42 @@
obj_list=new_obj_list
+asyncdefaprefetch_related_objects(model_instances,*related_lookups):
+"""See prefetch_related_objects()."""
+ returnawaitsync_to_async(prefetch_related_objects)(
+ model_instances,*related_lookups
+ )
+
+
defget_prefetcher(instance,through_attr,to_attr):""" For the attribute 'through_attr' on the given instance, find
- an object that has a get_prefetch_queryset().
+ an object that has a get_prefetch_querysets(). Return a 4 tuple containing:
- (the object with get_prefetch_queryset (or None),
+ (the object with get_prefetch_querysets (or None), the descriptor object representing this relationship (or None), a boolean that is False if the attribute was not found at all, a function that takes an instance and returns a boolean that is True if the attribute has already been fetched for that instance) """
- defhas_to_attr_attribute(instance):
- returnhasattr(instance,to_attr)
+ defis_to_attr_fetched(model,to_attr):
+ # Special case cached_property instances because hasattr() triggers
+ # attribute computation and assignment.
+ ifisinstance(getattr(model,to_attr,None),cached_property):
+
+ defhas_cached_property(instance):
+ returnto_attrininstance.__dict__
+
+ returnhas_cached_property
+
+ defhas_to_attr_attribute(instance):
+ returnhasattr(instance,to_attr)
+
+ returnhas_to_attr_attributeprefetcher=None
- is_fetched=has_to_attr_attribute
+ is_fetched=is_to_attr_fetched(instance.__class__,to_attr)# For singly related objects, we have to avoid getting the attribute# from the object, as this will trigger the query. So we first try
@@ -2490,29 +2568,31 @@
attr_found=Trueifrel_obj_descriptor:# singly related object, descriptor object has the
- # get_prefetch_queryset() method.
- ifhasattr(rel_obj_descriptor,"get_prefetch_queryset"):
+ # get_prefetch_querysets() method.
+ if(
+ hasattr(rel_obj_descriptor,"get_prefetch_querysets")
+ # RemovedInDjango60Warning.
+ orhasattr(rel_obj_descriptor,"get_prefetch_queryset")
+ ):prefetcher=rel_obj_descriptor
- is_fetched=rel_obj_descriptor.is_cached
+ # If to_attr is set, check if the value has already been set,
+ # which is done with has_to_attr_attribute(). Do not use the
+ # method from the descriptor, as the cache_name it defines
+ # checks the field name, not the to_attr value.
+ ifthrough_attr==to_attr:
+ is_fetched=rel_obj_descriptor.is_cachedelse:# descriptor doesn't support prefetching, so we go ahead and get# the attribute on the instance rather than the class to# support many related managersrel_obj=getattr(instance,through_attr)
- ifhasattr(rel_obj,"get_prefetch_queryset"):
+ if(
+ hasattr(rel_obj,"get_prefetch_querysets")
+ # RemovedInDjango60Warning.
+ orhasattr(rel_obj,"get_prefetch_queryset")
+ ):prefetcher=rel_obj
- ifthrough_attr!=to_attr:
- # Special case cached_property instances because hasattr
- # triggers attribute computation and assignment.
- ifisinstance(
- getattr(instance.__class__,to_attr,None),cached_property
- ):
-
- defhas_cached_property(instance):
- returnto_attrininstance.__dict__
-
- is_fetched=has_cached_property
- else:
+ ifthrough_attr==to_attr:defin_prefetched_cache(instance):returnthrough_attrininstance._prefetched_objects_cache
@@ -2531,7 +2611,7 @@
Return the prefetched objects along with any additional prefetches that must be done due to prefetch_related lookups found from default managers. """
- # prefetcher must have a method get_prefetch_queryset() which takes a list
+ # prefetcher must have a method get_prefetch_querysets() which takes a list# of instances, and returns a tuple:# (queryset of instances of self.model that are related to passed in instances,
@@ -2544,14 +2624,35 @@
# The 'values to be matched' must be hashable as they will be used# in a dictionary.
- (
- rel_qs,
- rel_obj_attr,
- instance_attr,
- single,
- cache_name,
- is_descriptor,
- )=prefetcher.get_prefetch_queryset(instances,lookup.get_current_queryset(level))
+ ifhasattr(prefetcher,"get_prefetch_querysets"):
+ (
+ rel_qs,
+ rel_obj_attr,
+ instance_attr,
+ single,
+ cache_name,
+ is_descriptor,
+ )=prefetcher.get_prefetch_querysets(
+ instances,lookup.get_current_querysets(level)
+ )
+ else:
+ warnings.warn(
+ "The usage of get_prefetch_queryset() in prefetch_related_objects() is "
+ "deprecated. Implement get_prefetch_querysets() instead.",
+ RemovedInDjango60Warning,
+ stacklevel=2,
+ )
+ queryset=None
+ ifquerysets:=lookup.get_current_querysets(level):
+ queryset=querysets[0]
+ (
+ rel_qs,
+ rel_obj_attr,
+ instance_attr,
+ single,
+ cache_name,
+ is_descriptor,
+ )=prefetcher.get_prefetch_queryset(instances,queryset)# We have to handle the possibility that the QuerySet we just got back# contains some prefetch_related lookups. We don't want to trigger the# prefetch_related functionality by evaluating the query. Rather, we need
diff --git a/docs/latest/_modules/django/db/models/query_utils.html b/docs/latest/_modules/django/db/models/query_utils.html
index 5c2421a46c..1b5e52c7c4 100644
--- a/docs/latest/_modules/django/db/models/query_utils.html
+++ b/docs/latest/_modules/django/db/models/query_utils.html
@@ -96,6 +96,7 @@
large and/or so that they can be used by other modules without getting intocircular import difficulties."""
+
importfunctoolsimportinspectimportlogging
@@ -105,6 +106,8 @@
fromdjango.dbimportDEFAULT_DB_ALIAS,DatabaseError,connectionsfromdjango.db.models.constantsimportLOOKUP_SEPfromdjango.utilsimporttree
+fromdjango.utils.functionalimportcached_property
+fromdjango.utils.hashableimportmake_hashablelogger=logging.getLogger("django.db.models")
@@ -242,6 +245,40 @@
kwargs["_negated"]=Truereturnpath,args,kwargs
+ @cached_property
+ defidentity(self):
+ path,args,kwargs=self.deconstruct()
+ identity=[path,*kwargs.items()]
+ forchildinargs:
+ ifisinstance(child,tuple):
+ arg,value=child
+ value=make_hashable(value)
+ identity.append((arg,value))
+ else:
+ identity.append(child)
+ returntuple(identity)
+
+ def__eq__(self,other):
+ ifnotisinstance(other,Q):
+ returnNotImplemented
+ returnother.identity==self.identity
+
+ def__hash__(self):
+ returnhash(self.identity)
+
+ @cached_property
+ defreferenced_base_fields(self):
+"""
+ Retrieve all base fields referenced directly or through F expressions
+ excluding any fields referenced through joins.
+ """
+ # Avoid circular imports.
+ fromdjango.db.models.sqlimportquery
+
+ return{
+ child.split(LOOKUP_SEP,1)[0]forchildinquery.get_children_from_q(self)
+ }
+
classDeferredAttribute:"""
@@ -266,6 +303,10 @@
# might be able to reuse the already loaded value. Refs #18343.val=self._check_parent_chain(instance)ifvalisNone:
+ ifinstance.pkisNoneandself.field.generated:
+ raiseAttributeError(
+ "Cannot read a generated field from an unsaved model."
+ )instance.refresh_from_db(fields=[field_name])else:data[field_name]=val
@@ -304,7 +345,7 @@
def_get_lookup(self,lookup_name):returnself.get_lookups().get(lookup_name,None)
- @functools.lru_cache(maxsize=None)
+ @functools.cachedefget_class_lookups(cls):class_lookups=[parent.__dict__.get("class_lookups",{})forparentininspect.getmro(cls)
@@ -403,38 +444,37 @@
_unregister_class_lookup=classmethod(_unregister_class_lookup)
-defselect_related_descend(field,restricted,requested,select_mask,reverse=False):
+defselect_related_descend(field,restricted,requested,select_mask):"""
- Return True if this field should be used to descend deeper for
- select_related() purposes. Used by both the query construction code
- (compiler.get_related_selections()) and the model instance creation code
- (compiler.klass_info).
+ Return whether `field` should be used to descend deeper for
+ `select_related()` purposes. Arguments:
- * field - the field to be checked
- * restricted - a boolean field, indicating if the field list has been
- manually restricted using a requested clause)
- * requested - The select_related() dictionary.
- * select_mask - the dictionary of selected fields.
- * reverse - boolean, True if we are checking a reverse select related
+ * `field` - the field to be checked. Can be either a `Field` or
+ `ForeignObjectRel` instance.
+ * `restricted` - a boolean field, indicating if the field list has been
+ manually restricted using a select_related() clause.
+ * `requested` - the select_related() dictionary.
+ * `select_mask` - the dictionary of selected fields. """
+ # Only relationships can be descended.ifnotfield.remote_field:returnFalse
- iffield.remote_field.parent_linkandnotreverse:
+ # Forward MTI parent links should not be explicitly descended as they are
+ # always JOIN'ed against (unless excluded by `select_mask`).
+ ifgetattr(field.remote_field,"parent_link",False):returnFalse
- ifrestricted:
- ifreverseandfield.related_query_name()notinrequested:
- returnFalse
- ifnotreverseandfield.namenotinrequested:
- returnFalse
- ifnotrestrictedandfield.null:
+ # When `select_related()` is used without a `*requested` mask all
+ # relationships are descended unless they are nullable.
+ ifnotrestricted:
+ returnnotfield.null
+ # When `select_related(*requested)` is used only fields that are part of
+ # `requested` should be descended.
+ iffield.namenotinrequested:returnFalse
- if(
- restricted
- andselect_mask
- andfield.nameinrequested
- andfieldnotinselect_mask
- ):
+ # Prevent invalid usages of `select_related()` and `only()`/`defer()` such
+ # as `select_related("a").only("b")` and `select_related("a").defer("a")`.
+ ifselect_maskandfieldnotinselect_mask:raiseFieldError(f"Field {field.model._meta.object_name}.{field.name} cannot be both ""deferred and traversed using select_related at the same time."
@@ -466,8 +506,8 @@
defcheck(opts):return(model._meta.concrete_model==opts.concrete_model
- oropts.concrete_modelinmodel._meta.get_parent_list()
- ormodelinopts.get_parent_list()
+ oropts.concrete_modelinmodel._meta.all_parents
+ ormodelinopts.all_parents)# If the field is a primary key, then doing a query against the field's
@@ -494,8 +534,11 @@
self.alias=Noneifnotisinstance(condition,Q):raiseValueError("condition argument must be a Q() instance.")
+ # .condition and .resolved_condition have to be stored independently
+ # as the former must remain unchanged for Join.__eq__ to remain stable
+ # and reusable even once their .filtered_relation are resolved.self.condition=condition
- self.path=[]
+ self.resolved_condition=Nonedef__eq__(self,other):ifnotisinstance(other,self.__class__):
@@ -509,21 +552,29 @@
defclone(self):clone=FilteredRelation(self.relation_name,condition=self.condition)clone.alias=self.alias
- clone.path=self.path[:]
+ if(resolved_condition:=self.resolved_condition)isnotNone:
+ clone.resolved_condition=resolved_condition.clone()returnclone
- defresolve_expression(self,*args,**kwargs):
-"""
- QuerySet.annotate() only accepts expression-like arguments
- (with a resolve_expression() method).
- """
- raiseNotImplementedError("FilteredRelation.resolve_expression() is unused.")
+ defrelabeled_clone(self,change_map):
+ clone=self.clone()
+ ifresolved_condition:=clone.resolved_condition:
+ clone.resolved_condition=resolved_condition.relabeled_clone(change_map)
+ returnclone
+
+ defresolve_expression(self,query,reuse,*args,**kwargs):
+ clone=self.clone()
+ clone.resolved_condition=query.build_filter(
+ self.condition,
+ can_reuse=reuse,
+ allow_joins=True,
+ split_subq=False,
+ update_join_types=False,
+ )[0]
+ returnclonedefas_sql(self,compiler,connection):
- # Resolve the condition in Join.filtered_relation.
- query=compiler.query
- where=query.build_filtered_relation_q(self.condition,reuse=set(self.path))
- returncompiler.compile(where)
+ returncompiler.compile(self.resolved_condition)
diff --git a/docs/latest/_modules/django/utils/functional.html b/docs/latest/_modules/django/utils/functional.html
index 5071c1a8c8..3237678314 100644
--- a/docs/latest/_modules/django/utils/functional.html
+++ b/docs/latest/_modules/django/utils/functional.html
@@ -92,8 +92,7 @@
importcopyimportitertoolsimportoperator
-importwarnings
-fromfunctoolsimporttotal_ordering,wraps
+fromfunctoolsimportwrapsclasscached_property:
@@ -114,16 +113,7 @@
"__set_name__() on it.")
- def__init__(self,func,name=None):
- fromdjango.utils.deprecationimportRemovedInDjango50Warning
-
- ifnameisnotNone:
- warnings.warn(
- "The name argument is deprecated as it's unnecessary as of "
- "Python 3.6.",
- RemovedInDjango50Warning,
- stacklevel=2,
- )
+ def__init__(self,func):self.real_func=funcself.__doc__=getattr(func,"__doc__")
@@ -183,7 +173,6 @@
function is evaluated on every access. """
- @total_orderingclass__proxy__(Promise):""" Encapsulate a function call and act as a proxy for methods that are
@@ -191,103 +180,16 @@
until one of the methods on the result is called. """
- __prepared=False
-
def__init__(self,args,kw):
- self.__args=args
- self.__kw=kw
- ifnotself.__prepared:
- self.__prepare_class__()
- self.__class__.__prepared=True
+ self._args=args
+ self._kw=kwdef__reduce__(self):return(_lazy_proxy_unpickle,
- (func,self.__args,self.__kw)+resultclasses,
+ (func,self._args,self._kw)+resultclasses,)
- def__repr__(self):
- returnrepr(self.__cast())
-
- @classmethod
- def__prepare_class__(cls):
- forresultclassinresultclasses:
- fortype_inresultclass.mro():
- formethod_nameintype_.__dict__:
- # All __promise__ return the same wrapper method, they
- # look up the correct implementation when called.
- ifhasattr(cls,method_name):
- continue
- meth=cls.__promise__(method_name)
- setattr(cls,method_name,meth)
- cls._delegate_bytes=bytesinresultclasses
- cls._delegate_text=strinresultclasses
- ifcls._delegate_bytesandcls._delegate_text:
- raiseValueError(
- "Cannot call lazy() with both bytes and text return types."
- )
- ifcls._delegate_text:
- cls.__str__=cls.__text_cast
- elifcls._delegate_bytes:
- cls.__bytes__=cls.__bytes_cast
-
- @classmethod
- def__promise__(cls,method_name):
- # Builds a wrapper around some magic method
- def__wrapper__(self,*args,**kw):
- # Automatically triggers the evaluation of a lazy value and
- # applies the given magic method of the result type.
- res=func(*self.__args,**self.__kw)
- returngetattr(res,method_name)(*args,**kw)
-
- return__wrapper__
-
- def__text_cast(self):
- returnfunc(*self.__args,**self.__kw)
-
- def__bytes_cast(self):
- returnbytes(func(*self.__args,**self.__kw))
-
- def__bytes_cast_encoded(self):
- returnfunc(*self.__args,**self.__kw).encode()
-
- def__cast(self):
- ifself._delegate_bytes:
- returnself.__bytes_cast()
- elifself._delegate_text:
- returnself.__text_cast()
- else:
- returnfunc(*self.__args,**self.__kw)
-
- def__str__(self):
- # object defines __str__(), so __prepare_class__() won't overload
- # a __str__() method from the proxied class.
- returnstr(self.__cast())
-
- def__eq__(self,other):
- ifisinstance(other,Promise):
- other=other.__cast()
- returnself.__cast()==other
-
- def__lt__(self,other):
- ifisinstance(other,Promise):
- other=other.__cast()
- returnself.__cast()<other
-
- def__hash__(self):
- returnhash(self.__cast())
-
- def__mod__(self,rhs):
- ifself._delegate_text:
- returnstr(self)%rhs
- returnself.__cast()%rhs
-
- def__add__(self,other):
- returnself.__cast()+other
-
- def__radd__(self,other):
- returnother+self.__cast()
-
def__deepcopy__(self,memo):# Instances of this class are effectively immutable. It's just a# collection of functions. So we don't need to do anything
@@ -295,6 +197,89 @@
memo[id(self)]=selfreturnself
+ def__cast(self):
+ returnfunc(*self._args,**self._kw)
+
+ # Explicitly wrap methods which are defined on object and hence would
+ # not have been overloaded by the loop over resultclasses below.
+
+ def__repr__(self):
+ returnrepr(self.__cast())
+
+ def__str__(self):
+ returnstr(self.__cast())
+
+ def__eq__(self,other):
+ ifisinstance(other,Promise):
+ other=other.__cast()
+ returnself.__cast()==other
+
+ def__ne__(self,other):
+ ifisinstance(other,Promise):
+ other=other.__cast()
+ returnself.__cast()!=other
+
+ def__lt__(self,other):
+ ifisinstance(other,Promise):
+ other=other.__cast()
+ returnself.__cast()<other
+
+ def__le__(self,other):
+ ifisinstance(other,Promise):
+ other=other.__cast()
+ returnself.__cast()<=other
+
+ def__gt__(self,other):
+ ifisinstance(other,Promise):
+ other=other.__cast()
+ returnself.__cast()>other
+
+ def__ge__(self,other):
+ ifisinstance(other,Promise):
+ other=other.__cast()
+ returnself.__cast()>=other
+
+ def__hash__(self):
+ returnhash(self.__cast())
+
+ def__format__(self,format_spec):
+ returnformat(self.__cast(),format_spec)
+
+ # Explicitly wrap methods which are required for certain operations on
+ # int/str objects to function correctly.
+
+ def__add__(self,other):
+ returnself.__cast()+other
+
+ def__radd__(self,other):
+ returnother+self.__cast()
+
+ def__mod__(self,other):
+ returnself.__cast()%other
+
+ def__mul__(self,other):
+ returnself.__cast()*other
+
+ # Add wrappers for all methods from resultclasses which haven't been
+ # wrapped explicitly above.
+ forresultclassinresultclasses:
+ fortype_inresultclass.mro():
+ formethod_nameintype_.__dict__:
+ # All __promise__ return the same wrapper method, they look up
+ # the correct implementation when called.
+ ifhasattr(__proxy__,method_name):
+ continue
+
+ # Builds a wrapper around some method. Pass method_name to
+ # avoid issues due to late binding.
+ def__wrapper__(self,*args,__method_name=method_name,**kw):
+ # Automatically triggers the evaluation of a lazy value and
+ # applies the given method of the result type.
+ result=func(*self._args,**self._kw)
+ returngetattr(result,__method_name)(*args,**kw)
+
+ setattr(__proxy__,method_name,__wrapper__)
+
@wraps(func)def__wrapper__(*args,**kw):# Creates the proxy object, instead of the actual value.
diff --git a/docs/latest/_modules/evennia/contrib/base_systems/awsstorage/tests.html b/docs/latest/_modules/evennia/contrib/base_systems/awsstorage/tests.html
index 01b503ff77..2fad181366 100644
--- a/docs/latest/_modules/evennia/contrib/base_systems/awsstorage/tests.html
+++ b/docs/latest/_modules/evennia/contrib/base_systems/awsstorage/tests.html
@@ -100,7 +100,7 @@
fromdjango.core.exceptionsimportImproperlyConfiguredfromdjango.core.files.baseimportContentFilefromdjango.testimportTestCase,override_settings
-fromdjango.utils.timezoneimportis_aware,utc
+fromdjango.utils.timezoneimportis_aware_SKIP=Falsetry:
@@ -625,7 +625,7 @@
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(
diff --git a/docs/latest/_modules/evennia/server/evennia_launcher.html b/docs/latest/_modules/evennia/server/evennia_launcher.html
index 261d4c754f..39fb8caa18 100644
--- a/docs/latest/_modules/evennia/server/evennia_launcher.html
+++ b/docs/latest/_modules/evennia/server/evennia_launcher.html
@@ -1576,69 +1576,78 @@
[docs]defcheck_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
+ fromdjango.confimportsettingsfromdjango.dbimportconnection
- tables=connection.introspection.get_table_list(connection.cursor())
- ifnottablesornotisinstance(tables[0],str):# django 1.8+
- tables=[tableinfo.namefortableinfointables]
- iftablesand"accounts_accountdb"intables:
- # database exists and seems set up. Initialize evennia.
- evennia._init()
- # Try to get Account#1
- fromevennia.accounts.modelsimportAccountDB
+ 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)ase:
- ifalways_return:
- returnFalse
- print(ERROR_DATABASE.format(traceback=e))
- sys.exit()
- exceptAccountDB.DoesNotExist:
- # no superuser yet. We need to create it.
+ withconnection.cursor()ascursor:
+ # Get all table names in the database
+ ifconnection.vendor=="postgresql":
+ cursor.execute(
+"""
+ SELECT tablename FROM pg_tables
+ WHERE schemaname = 'public'
+ """
+ )
+ elifconnection.vendor=="mysql":
+ cursor.execute(
+"""
+ SELECT table_name FROM information_schema.tables
+ WHERE table_schema = %s
+ """,
+ [settings.DATABASES["default"]["NAME"]],
+ )
+ elifconnection.vendor=="sqlite":
+ cursor.execute(
+"""
+ SELECT name FROM sqlite_master
+ WHERE type='table' AND name NOT LIKE 'sqlite_%'
+ """
+ )
+ else:
+ ifnotalways_return:
+ raiseException(f"Unsupported database vendor: {connection.vendor}")
+ returnFalse
- other_superuser=AccountDB.objects.filter(is_superuser=True)
- ifother_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=""
- whileres.upper()!="Y":
- # ask for permission
- res=eval(input("Continue [Y]/N: "))
- ifres.upper()=="N":
- sys.exit()
- elifnotres:
- break
- # continue with the
- fromcopyimportdeepcopy
+ existing_tables={row[0].lower()forrowincursor.fetchall()}
- new=deepcopy(other)
- other.delete()
- new.id=1
- new.save()
- else:
- create_superuser()
- check_database(always_return=always_return)
- returnTrue
+"""
+This module gathers all the essential database-creation functions for the game
+engine's various object types.
+
+Only objects created 'stand-alone' are in here. E.g. object Attributes are
+always created through their respective objects handlers.
+
+Each `creation_*` function also has an alias named for the entity being created,
+such as create_object() and object(). This is for consistency with the
+utils.search module and allows you to do the shorter `create.object()`.
+
+The respective object managers hold more methods for manipulating and searching
+objects already existing in the database.
+
+"""
+
+fromdjango.utils.functionalimportSimpleLazyObject
+
+# limit symbol import from API
+__all__=(
+ "create_object",
+ "create_script",
+ "create_help_entry",
+ "create_message",
+ "create_channel",
+ "create_account",
+)
+
+_GA=object.__getattribute__
+
+
+# Lazy-loaded model classes
+def_get_objectdb():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="objects",model="objectdb").model_class()
+
+
+def_get_scriptdb():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="scripts",model="scriptdb").model_class()
+
+
+def_get_accountdb():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="accounts",model="accountdb").model_class()
+
+
+def_get_msg():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="comms",model="msg").model_class()
+
+
+def_get_channeldb():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="comms",model="channeldb").model_class()
+
+
+def_get_helpentry():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="help",model="helpentry").model_class()
+
+
+def_get_tag():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.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)
+
+
+
[docs]defcreate_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.
+ """
+ returnObjectDB.objects.create_object(*args,**kwargs)
+
+
+
[docs]defcreate_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.
+ """
+ returnScriptDB.objects.create_script(*args,**kwargs)
+
+
+
[docs]defcreate_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.
+ """
+ returnHelpEntry.objects.create_help(*args,**kwargs)
+
+
+
[docs]defcreate_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.
+ """
+ returnMsg.objects.create_message(*args,**kwargs)
+
+
+
[docs]defcreate_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.
+ """
+ returnChannelDB.objects.create_channel(*args,**kwargs)
+
+
+
[docs]defcreate_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.
+ """
+ returnAccountDB.objects.create_account(*args,**kwargs)
+
+
+# Aliases for the creation functions
+object=create_object
+script=create_script
+help_entry=create_help_entry
+message=create_message
+channel=create_channel
+account=create_account
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/latest/_modules/evennia/utils/search.html b/docs/latest/_modules/evennia/utils/search.html
index 0401541e41..d1c3b86d1d 100644
--- a/docs/latest/_modules/evennia/utils/search.html
+++ b/docs/latest/_modules/evennia/utils/search.html
@@ -113,13 +113,9 @@
> from evennia.objects.models import ObjectDB> match = Object.objects.get_object_with_account(...)
-
"""
-# Import the manager methods to be wrapped
-
-fromdjango.contrib.contenttypes.modelsimportContentType
-fromdjango.db.utilsimportOperationalError,ProgrammingError
+fromdjango.utils.functionalimportSimpleLazyObject# limit symbol import from API__all__=(
@@ -137,180 +133,265 @@
)
-# 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.")
- fromevennia.accounts.modelsimportAccountDB
- fromevennia.comms.modelsimportChannelDB,Msg
- fromevennia.help.modelsimportHelpEntry
- fromevennia.objects.modelsimportObjectDB
- fromevennia.scripts.modelsimportScriptDB
- fromevennia.typeclasses.tagsimportTag# noqa
+# Lazy-loaded model classes
+def_get_objectdb():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="objects",model="objectdb").model_class()
+
+
+def_get_accountdb():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="accounts",model="accountdb").model_class()
+
+
+def_get_scriptdb():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="scripts",model="scriptdb").model_class()
+
+
+def_get_msg():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="comms",model="msg").model_class()
+
+
+def_get_channeldb():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="comms",model="channeldb").model_class()
+
+
+def_get_helpentry():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.objects.get(app_label="help",model="helpentry").model_class()
+
+
+def_get_tag():
+ fromdjango.contrib.contenttypes.modelsimportContentType
+
+ returnContentType.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
+
+
[docs]defsearch_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.
+ """
+ returnObjectDB.objects.search_object(*args,**kwargs)
+
+
search_objects=search_objectobject_search=search_objectobjects=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.
-#
+
[docs]defsearch_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.
+ """
+ returnAccountDB.objects.search_account(*args,**kwargs)
+
-search_account=AccountDB.objects.search_accountsearch_accounts=search_accountaccount_search=search_accountaccounts=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
+
[docs]defsearch_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.
+ """
+ returnScriptDB.objects.search_script(*args,**kwargs)
+
+
search_scripts=search_scriptscript_search=search_scriptscripts=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
+
+
[docs]defsearch_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.
+ """
+ returnMsg.objects.search_message(*args,**kwargs)
+
+
search_messages=search_messagemessage_search=search_messagemessages=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
+
[docs]defsearch_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.
+ """
+ returnChannelDB.objects.search_channel(*args,**kwargs)
+
+
search_channels=search_channelchannel_search=search_channelchannels=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
+defsearch_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.
+ """
+ returnHelpEntry.objects.search_help(*args,**kwargs)
+
+
search_help_entry=search_helpsearch_help_entries=search_helphelp_entry_search=search_helphelp_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)
-
-
defsearch_object_attribute(key=None,category=None,value=None,strvalue=None,attrtype=None,**kwargs):
+"""
+ Search for objects by their attributes.
+ """returnObjectDB.objects.get_by_attribute(key=key,category=category,value=value,strvalue=strvalue,attrtype=attrtype,**kwargs)
@@ -319,6 +400,9 @@
defsearch_account_attribute(key=None,category=None,value=None,strvalue=None,attrtype=None,**kwargs):
+"""
+ Search for accounts by their attributes.
+ """returnAccountDB.objects.get_by_attribute(key=key,category=category,value=value,strvalue=strvalue,attrtype=attrtype,**kwargs)
@@ -327,6 +411,9 @@
defsearch_script_attribute(key=None,category=None,value=None,strvalue=None,attrtype=None,**kwargs):
+"""
+ Search for scripts by their attributes.
+ """returnScriptDB.objects.get_by_attribute(key=key,category=category,value=value,strvalue=strvalue,attrtype=attrtype,**kwargs)
@@ -335,23 +422,20 @@
defsearch_channel_attribute(key=None,category=None,value=None,strvalue=None,attrtype=None,**kwargs):
+"""
+ Search for channels by their attributes.
+ """returnChannelDB.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
+defsearch_attribute_object(*args,**kwargs):
+"""
+ Search for attribute objects.
+ """
+ returnObjectDB.objects.get_attribute(*args,**kwargs)defsearch_object_by_tag(key=None,category=None,tagtype=None,**kwargs):
@@ -373,7 +457,6 @@
matches (list): List of Objects with tags matching the search criteria, or an empty list if no matches were found.
-
"""returnObjectDB.objects.get_by_tag(key=key,category=category,tagtype=tagtype,**kwargs)
@@ -384,23 +467,6 @@
[docs]defsearch_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.
-
"""returnAccountDB.objects.get_by_tag(key=key,category=category,tagtype=tagtype,**kwargs)
@@ -408,23 +474,6 @@
[docs]defsearch_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.
-
"""returnScriptDB.objects.get_by_tag(key=key,category=category,tagtype=tagtype,**kwargs)
@@ -432,35 +481,16 @@
[docs]defsearch_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.
-
"""returnChannelDB.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
+defsearch_tag_object(*args,**kwargs):
+"""
+ Search for tag objects.
+ """
+ returnObjectDB.objects.get_tag(*args,**kwargs)defsearch_objects_by_typeclass(typeclass,include_children=False,include_parents=False):
diff --git a/docs/latest/_modules/evennia/web/website/tests.html b/docs/latest/_modules/evennia/web/website/tests.html
index 881c363a5f..c2c10adb5d 100644
--- a/docs/latest/_modules/evennia/web/website/tests.html
+++ b/docs/latest/_modules/evennia/web/website/tests.html
@@ -187,7 +187,21 @@
[docs]deftest_get(self):
+"""Since Django 5.0, logout is no longer supported with GET requests"""
+ pass
+
+
[docs]deftest_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)
+
+
[docs]deftest_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)
diff --git a/docs/latest/_sources/Coding/Changelog.md.txt b/docs/latest/_sources/Coding/Changelog.md.txt
index 4ee241282c..4b2b0cebde 100644
--- a/docs/latest/_sources/Coding/Changelog.md.txt
+++ b/docs/latest/_sources/Coding/Changelog.md.txt
@@ -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
diff --git a/docs/latest/api/evennia.commands.default.batchprocess.html b/docs/latest/api/evennia.commands.default.batchprocess.html
index 9b80e97757..5a7b88c2de 100644
--- a/docs/latest/api/evennia.commands.default.batchprocess.html
+++ b/docs/latest/api/evennia.commands.default.batchprocess.html
@@ -152,7 +152,7 @@ skipping, reloading etc.
-search_index_entry = {'aliases': 'batchcommand batchcmd', 'category': 'building', 'key': 'batchcommands', 'no_prefix': ' batchcommand batchcmd', 'tags': '', 'text': '\n build from batch-command file\n\n Usage:\n batchcommands[/interactive] <python.path.to.file>\n\n Switch:\n interactive - this mode will offer more control when\n executing the batch file, like stepping,\n skipping, reloading etc.\n\n Runs batches of commands from a batch-cmd text file (*.ev).\n\n '}¶
+search_index_entry = {'aliases': 'batchcmd batchcommand', 'category': 'building', 'key': 'batchcommands', 'no_prefix': ' batchcmd batchcommand', 'tags': '', 'text': '\n build from batch-command file\n\n Usage:\n batchcommands[/interactive] <python.path.to.file>\n\n Switch:\n interactive - this mode will offer more control when\n executing the batch file, like stepping,\n skipping, reloading etc.\n\n Runs batches of commands from a batch-cmd text file (*.ev).\n\n '}¶
diff --git a/docs/latest/api/evennia.commands.default.building.html b/docs/latest/api/evennia.commands.default.building.html
index 30f819d325..5a0ba4f902 100644
--- a/docs/latest/api/evennia.commands.default.building.html
+++ b/docs/latest/api/evennia.commands.default.building.html
@@ -1419,7 +1419,7 @@ server settings.
-search_index_entry = {'aliases': '@update @type @parent @swap @typeclasses', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass update type parent swap typeclasses', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclasses or typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object. This will also\n reset cmdsets!\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}¶
+search_index_entry = {'aliases': '@parent @update @swap @type @typeclasses', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass parent update swap type typeclasses', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclasses or typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object. This will also\n reset cmdsets!\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}¶
@@ -1605,7 +1605,7 @@ If object is not specified, the current location is examined.
@@ -1878,7 +1878,7 @@ the cases, see the module doc.
-search_index_entry = {'aliases': '@exam @ex', 'category': 'building', 'key': '@examine', 'no_prefix': 'examine exam ex', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n script - examine a Script\n channel - examine a Channel\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}¶
+search_index_entry = {'aliases': '@ex @exam', 'category': 'building', 'key': '@examine', 'no_prefix': 'examine ex exam', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n script - examine a Script\n channel - examine a Channel\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}¶
-search_index_entry = {'aliases': '@locate @search', 'category': 'building', 'key': '@find', 'no_prefix': 'find locate search', 'tags': '', 'text': '\n search the database for objects\n\n Usage:\n find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]]\n locate - this is a shorthand for using the /loc switch.\n\n Switches:\n room - only look for rooms (location=None)\n exit - only look for exits (destination!=None)\n char - only look for characters (BASE_CHARACTER_TYPECLASS)\n exact - only exact matches are returned.\n loc - display object location if exists and match has one result\n startswith - search for names starting with the string, rather than containing\n\n Searches the database for an object of a particular name or exact #dbref.\n Use *accountname to search for an account. The switches allows for\n limiting object matches to certain game entities. Dbrefmin and dbrefmax\n limits matches to within the given dbrefs range, or above/below if only\n one is given.\n '}¶
+search_index_entry = {'aliases': '@search @locate', 'category': 'building', 'key': '@find', 'no_prefix': 'find search locate', 'tags': '', 'text': '\n search the database for objects\n\n Usage:\n find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]]\n locate - this is a shorthand for using the /loc switch.\n\n Switches:\n room - only look for rooms (location=None)\n exit - only look for exits (destination!=None)\n char - only look for characters (BASE_CHARACTER_TYPECLASS)\n exact - only exact matches are returned.\n loc - display object location if exists and match has one result\n startswith - search for names starting with the string, rather than containing\n\n Searches the database for an object of a particular name or exact #dbref.\n Use *accountname to search for an account. The switches allows for\n limiting object matches to certain game entities. Dbrefmin and dbrefmax\n limits matches to within the given dbrefs range, or above/below if only\n one is given.\n '}¶
diff --git a/docs/latest/api/evennia.commands.default.comms.html b/docs/latest/api/evennia.commands.default.comms.html
index 1f35294a28..0fa48db791 100644
--- a/docs/latest/api/evennia.commands.default.comms.html
+++ b/docs/latest/api/evennia.commands.default.comms.html
@@ -270,7 +270,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
-search_index_entry = {'aliases': '@channels @chan', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel channels chan', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶
+search_index_entry = {'aliases': '@chan @channels', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel chan channels', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶
diff --git a/docs/latest/api/evennia.commands.default.general.html b/docs/latest/api/evennia.commands.default.general.html
index ff5f2e28e6..e92d9873da 100644
--- a/docs/latest/api/evennia.commands.default.general.html
+++ b/docs/latest/api/evennia.commands.default.general.html
@@ -282,7 +282,7 @@ for everyone to use, you need build privileges and the alias command.
@@ -314,7 +314,7 @@ for everyone to use, you need build privileges and the alias command.
-search_index_entry = {'aliases': 'nickname nicks', 'category': 'general', 'key': 'nick', 'no_prefix': ' nickname nicks', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\\\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}¶
+search_index_entry = {'aliases': 'nicks nickname', 'category': 'general', 'key': 'nick', 'no_prefix': ' nicks nickname', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\\\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}¶
-search_index_entry = {'aliases': ': emote', 'category': 'general', 'key': 'pose', 'no_prefix': ' : emote', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}¶
+search_index_entry = {'aliases': 'emote :', 'category': 'general', 'key': 'pose', 'no_prefix': ' emote :', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}¶
diff --git a/docs/latest/api/evennia.commands.default.tests.html b/docs/latest/api/evennia.commands.default.tests.html
index b667304f7b..898d00361f 100644
--- a/docs/latest/api/evennia.commands.default.tests.html
+++ b/docs/latest/api/evennia.commands.default.tests.html
@@ -985,7 +985,7 @@ main test suite started with
Test the batch processor.
-red_button = <module 'evennia.contrib.tutorials.red_button.red_button' from '/tmp/tmp5jf4_6kl/4269745d3ef8708761098cfcb9310d86e44e31c1/evennia/contrib/tutorials/red_button/red_button.py'>¶
+red_button = <module 'evennia.contrib.tutorials.red_button.red_button' from '/tmp/tmp6kehfgj8/0f28eb1ac38eef3e8b4c6fcc82c35ec25a53eda0/evennia/contrib/tutorials/red_button/red_button.py'>¶
@@ -171,7 +171,7 @@ there is no object yet before the account has logged in)
-search_index_entry = {'aliases': 'con conn co', 'category': 'general', 'key': 'connect', 'no_prefix': ' con conn co', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶
+search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'no_prefix': ' conn co con', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶
@@ -306,7 +306,7 @@ All it does is display the connect screen.
@@ -332,7 +332,7 @@ All it does is display the connect screen.
-search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' look l', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
+search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' l look', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
@@ -355,7 +355,7 @@ for simplicity. It shows a pane of info.
@@ -381,7 +381,7 @@ for simplicity. It shows a pane of info.
-search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'no_prefix': ' ? h', 'tags': '', 'text': '\n get help when in unconnected-in state\n\n Usage:\n help\n\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}¶
+search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'no_prefix': ' h ?', 'tags': '', 'text': '\n get help when in unconnected-in state\n\n Usage:\n help\n\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}¶
diff --git a/docs/latest/api/evennia.contrib.base_systems.email_login.email_login.html b/docs/latest/api/evennia.contrib.base_systems.email_login.email_login.html
index e48e6ca02b..b72c64e927 100644
--- a/docs/latest/api/evennia.contrib.base_systems.email_login.email_login.html
+++ b/docs/latest/api/evennia.contrib.base_systems.email_login.email_login.html
@@ -153,7 +153,7 @@ the module given by settings.CONNECTION_SCREEN_MODULE.
@@ -183,7 +183,7 @@ there is no object yet before the account has logged in)
-search_index_entry = {'aliases': 'con conn co', 'category': 'general', 'key': 'connect', 'no_prefix': ' con conn co', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}¶
+search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'no_prefix': ' conn co con', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}¶
@@ -311,7 +311,7 @@ All it does is display the connect screen.
@@ -337,7 +337,7 @@ All it does is display the connect screen.
-search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' look l', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
+search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' l look', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
@@ -355,7 +355,7 @@ for simplicity. It shows a pane of info.
@@ -381,7 +381,7 @@ for simplicity. It shows a pane of info.
-search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'no_prefix': ' ? h', 'tags': '', 'text': '\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}¶
+search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'no_prefix': ' h ?', 'tags': '', 'text': '\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}¶
@@ -205,7 +205,7 @@ aliases to an already joined channel.
-search_index_entry = {'aliases': 'chanalias aliaschan', 'category': 'comms', 'key': 'addcom', 'no_prefix': ' chanalias aliaschan', 'tags': '', 'text': '\n Add a channel alias and/or subscribe to a channel\n\n Usage:\n addcom [alias=] <channel>\n\n Joins a given channel. If alias is given, this will allow you to\n refer to the channel by this alias rather than the full channel\n name. Subsequent calls of this command can be used to add multiple\n aliases to an already joined channel.\n '}¶
+search_index_entry = {'aliases': 'aliaschan chanalias', 'category': 'comms', 'key': 'addcom', 'no_prefix': ' aliaschan chanalias', 'tags': '', 'text': '\n Add a channel alias and/or subscribe to a channel\n\n Usage:\n addcom [alias=] <channel>\n\n Joins a given channel. If alias is given, this will allow you to\n refer to the channel by this alias rather than the full channel\n name. Subsequent calls of this command can be used to add multiple\n aliases to an already joined channel.\n '}¶
diff --git a/docs/latest/api/evennia.contrib.full_systems.evscaperoom.commands.html b/docs/latest/api/evennia.contrib.full_systems.evscaperoom.commands.html
index 7b0ddad276..1c6ee6e2f3 100644
--- a/docs/latest/api/evennia.contrib.full_systems.evscaperoom.commands.html
+++ b/docs/latest/api/evennia.contrib.full_systems.evscaperoom.commands.html
@@ -225,7 +225,7 @@ the operation will be general or on the room.
-search_index_entry = {'aliases': 'quit q chicken out abort', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' quit q chicken out abort', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}¶
+search_index_entry = {'aliases': 'quit abort q chicken out', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' quit abort q chicken out', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}¶
-search_index_entry = {'aliases': 'pose :', 'category': 'general', 'key': 'emote', 'no_prefix': ' pose :', 'tags': '', 'text': '\n Perform a free-form emote. Use /me to\n include yourself in the emote and /name\n to include other objects or characters.\n Use "..." to enact speech.\n\n Usage:\n emote <emote>\n :<emote\n\n Example:\n emote /me smiles at /peter\n emote /me points to /box and /lever.\n\n '}¶
+search_index_entry = {'aliases': ': pose', 'category': 'general', 'key': 'emote', 'no_prefix': ' : pose', 'tags': '', 'text': '\n Perform a free-form emote. Use /me to\n include yourself in the emote and /name\n to include other objects or characters.\n Use "..." to enact speech.\n\n Usage:\n emote <emote>\n :<emote\n\n Example:\n emote /me smiles at /peter\n emote /me points to /box and /lever.\n\n '}¶
@@ -504,7 +504,7 @@ looks and what actions is available.
-search_index_entry = {'aliases': 'examine e ex unfocus', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' examine e ex unfocus', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}¶
+search_index_entry = {'aliases': 'unfocus ex examine e', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' unfocus ex examine e', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}¶
-search_index_entry = {'aliases': 'inv inventory give i', 'category': 'evscaperoom', 'key': 'get', 'no_prefix': ' inv inventory give i', 'tags': '', 'text': '\n Use focus / examine instead.\n\n '}¶
+search_index_entry = {'aliases': 'i inv give inventory', 'category': 'evscaperoom', 'key': 'get', 'no_prefix': ' i inv give inventory', 'tags': '', 'text': '\n Use focus / examine instead.\n\n '}¶
diff --git a/docs/latest/api/evennia.contrib.game_systems.barter.barter.html b/docs/latest/api/evennia.contrib.game_systems.barter.barter.html
index 180d50f58c..995664a60e 100644
--- a/docs/latest/api/evennia.contrib.game_systems.barter.barter.html
+++ b/docs/latest/api/evennia.contrib.game_systems.barter.barter.html
@@ -759,7 +759,7 @@ try to influence the other part in the deal.
@@ -785,7 +785,7 @@ try to influence the other part in the deal.
-search_index_entry = {'aliases': 'offers deal', 'category': 'trading', 'key': 'status', 'no_prefix': ' offers deal', 'tags': '', 'text': "\n show a list of the current deal\n\n Usage:\n status\n deal\n offers\n\n Shows the currently suggested offers on each sides of the deal. To\n accept the current deal, use the 'accept' command. Use 'offer' to\n change your deal. You might also want to use 'say', 'emote' etc to\n try to influence the other part in the deal.\n "}¶
+search_index_entry = {'aliases': 'deal offers', 'category': 'trading', 'key': 'status', 'no_prefix': ' deal offers', 'tags': '', 'text': "\n show a list of the current deal\n\n Usage:\n status\n deal\n offers\n\n Shows the currently suggested offers on each sides of the deal. To\n accept the current deal, use the 'accept' command. Use 'offer' to\n change your deal. You might also want to use 'say', 'emote' etc to\n try to influence the other part in the deal.\n "}¶
diff --git a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_basic.html b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_basic.html
index c662d4ed02..9dd6a535f0 100644
--- a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_basic.html
+++ b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_basic.html
@@ -686,7 +686,7 @@ if there are still any actions you can take.
@@ -712,7 +712,7 @@ if there are still any actions you can take.
-search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
+search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
diff --git a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_equip.html b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_equip.html
index b43d0f5a48..0bed424fda 100644
--- a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_equip.html
+++ b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_equip.html
@@ -581,7 +581,7 @@ if there are still any actions you can take.
@@ -601,7 +601,7 @@ if there are still any actions you can take.
-search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
+search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
diff --git a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_items.html b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_items.html
index 1e2a340556..9b7607f915 100644
--- a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_items.html
+++ b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_items.html
@@ -704,7 +704,7 @@ if there are still any actions you can take.
@@ -724,7 +724,7 @@ if there are still any actions you can take.
-search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
+search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
diff --git a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_magic.html b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_magic.html
index 62ecd7c88e..0f29530eac 100644
--- a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_magic.html
+++ b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_magic.html
@@ -483,7 +483,7 @@ if there are still any actions you can take.
@@ -503,7 +503,7 @@ if there are still any actions you can take.
-search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
+search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
diff --git a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_range.html b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_range.html
index 66eedd87ae..d06a19f299 100644
--- a/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_range.html
+++ b/docs/latest/api/evennia.contrib.game_systems.turnbattle.tb_range.html
@@ -943,7 +943,7 @@ if there are still any actions you can take.
@@ -963,7 +963,7 @@ if there are still any actions you can take.
-search_index_entry = {'aliases': 'wait hold', 'category': 'combat', 'key': 'pass', 'no_prefix': ' wait hold', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
+search_index_entry = {'aliases': 'hold wait', 'category': 'combat', 'key': 'pass', 'no_prefix': ' hold wait', 'tags': '', 'text': '\n Passes on your turn.\n\n Usage:\n pass\n\n When in a fight, you can use this command to end your turn early, even\n if there are still any actions you can take.\n '}¶
diff --git a/docs/latest/api/evennia.contrib.rpg.dice.dice.html b/docs/latest/api/evennia.contrib.rpg.dice.dice.html
index de8840b0bd..9991833142 100644
--- a/docs/latest/api/evennia.contrib.rpg.dice.dice.html
+++ b/docs/latest/api/evennia.contrib.rpg.dice.dice.html
@@ -340,7 +340,7 @@ everyone but the person rolling.
@@ -366,7 +366,7 @@ everyone but the person rolling.
-search_index_entry = {'aliases': '@dice roll', 'category': 'general', 'key': 'dice', 'no_prefix': ' dice roll', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}¶
+search_index_entry = {'aliases': 'roll @dice', 'category': 'general', 'key': 'dice', 'no_prefix': ' roll dice', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}¶
diff --git a/docs/latest/api/evennia.contrib.rpg.rpsystem.rpsystem.html b/docs/latest/api/evennia.contrib.rpg.rpsystem.rpsystem.html
index c8ff424751..fd037d2f2b 100644
--- a/docs/latest/api/evennia.contrib.rpg.rpsystem.rpsystem.html
+++ b/docs/latest/api/evennia.contrib.rpg.rpsystem.rpsystem.html
@@ -908,7 +908,7 @@ Using the command without arguments will list all current recogs.
@@ -935,7 +935,7 @@ Using the command without arguments will list all current recogs.
-search_index_entry = {'aliases': 'recognize forget', 'category': 'general', 'key': 'recog', 'no_prefix': ' recognize forget', 'tags': '', 'text': '\n Recognize another person in the same room.\n\n Usage:\n recog\n recog sdesc as alias\n forget alias\n\n Example:\n recog tall man as Griatch\n forget griatch\n\n This will assign a personal alias for a person, or forget said alias.\n Using the command without arguments will list all current recogs.\n\n '}¶
+search_index_entry = {'aliases': 'forget recognize', 'category': 'general', 'key': 'recog', 'no_prefix': ' forget recognize', 'tags': '', 'text': '\n Recognize another person in the same room.\n\n Usage:\n recog\n recog sdesc as alias\n forget alias\n\n Example:\n recog tall man as Griatch\n forget griatch\n\n This will assign a personal alias for a person, or forget said alias.\n Using the command without arguments will list all current recogs.\n\n '}¶
diff --git a/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_turnbased.html b/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_turnbased.html
index 269a797481..5749a8d5e1 100644
--- a/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_turnbased.html
+++ b/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_turnbased.html
@@ -480,7 +480,7 @@ turn of combat, performing everyone’s actions in random order.
diff --git a/docs/latest/api/evennia.contrib.tutorials.red_button.red_button.html b/docs/latest/api/evennia.contrib.tutorials.red_button.red_button.html
index ccb100a04d..7d8a685b13 100644
--- a/docs/latest/api/evennia.contrib.tutorials.red_button.red_button.html
+++ b/docs/latest/api/evennia.contrib.tutorials.red_button.red_button.html
@@ -167,7 +167,7 @@ such as when closing the lid and un-blinding a character.
-search_index_entry = {'aliases': 'examine listen l feel ex get', 'category': 'general', 'key': 'look', 'no_prefix': ' examine listen l feel ex get', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}¶
+search_index_entry = {'aliases': 'l feel get listen examine ex', 'category': 'general', 'key': 'look', 'no_prefix': ' l feel get listen examine ex', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}¶
diff --git a/docs/latest/api/evennia.contrib.tutorials.tutorial_world.objects.html b/docs/latest/api/evennia.contrib.tutorials.tutorial_world.objects.html
index 3e4229e302..bc49fc2897 100644
--- a/docs/latest/api/evennia.contrib.tutorials.tutorial_world.objects.html
+++ b/docs/latest/api/evennia.contrib.tutorials.tutorial_world.objects.html
@@ -570,7 +570,7 @@ shift green root up/down
@@ -819,7 +819,7 @@ parry - forgoes your attack but will make you harder to hit on next
-search_index_entry = {'aliases': 'kill defend slash parry bash fight stab pierce chop hit thrust', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' kill defend slash parry bash fight stab pierce chop hit thrust', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}¶
+search_index_entry = {'aliases': 'bash defend slash pierce thrust hit chop parry fight stab kill', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' bash defend slash pierce thrust hit chop parry fight stab kill', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}¶
diff --git a/docs/latest/api/evennia.contrib.tutorials.tutorial_world.rooms.html b/docs/latest/api/evennia.contrib.tutorials.tutorial_world.rooms.html
index 69f794d3a6..6eea4b6ec2 100644
--- a/docs/latest/api/evennia.contrib.tutorials.tutorial_world.rooms.html
+++ b/docs/latest/api/evennia.contrib.tutorials.tutorial_world.rooms.html
@@ -830,7 +830,7 @@ if they fall off the bridge.
@@ -1010,7 +1010,7 @@ random chance of eventually finding a light source.
-search_index_entry = {'aliases': 'feel around l feel fiddle search', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' feel around l feel fiddle search', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}¶
+search_index_entry = {'aliases': 'l feel feel around search fiddle', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' l feel feel around search fiddle', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}¶
Check if the database exists and has basic tables. This is only run by the launcher.
Parameters
-
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.
+
+
+
diff --git a/docs/latest/api/evennia.utils.create.html b/docs/latest/api/evennia.utils.create.html
index b8b57ec565..b27cfc5c47 100644
--- a/docs/latest/api/evennia.utils.create.html
+++ b/docs/latest/api/evennia.utils.create.html
@@ -126,7 +126,7 @@ utils.search module and allows you to do the shorter create.object()
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
@@ -209,7 +209,7 @@ scripts in the database.
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
@@ -234,7 +234,7 @@ in-game setting information and so on.
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
@@ -294,7 +294,7 @@ often used).
-search_index_entry = {'aliases': 'abort yes a __nomatch_command no y n', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' abort yes a __nomatch_command no y n', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}¶
+search_index_entry = {'aliases': 'abort __nomatch_command yes a no n y', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' abort __nomatch_command yes a no n y', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}¶
diff --git a/docs/latest/api/evennia.utils.evmore.html b/docs/latest/api/evennia.utils.evmore.html
index cc3d198c19..f75c65defd 100644
--- a/docs/latest/api/evennia.utils.evmore.html
+++ b/docs/latest/api/evennia.utils.evmore.html
@@ -151,7 +151,7 @@ the caller.msg() construct every time the page is updated.
@@ -177,7 +177,7 @@ the caller.msg() construct every time the page is updated.
-search_index_entry = {'aliases': 't abort top e end quit a previous q p next n', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' t abort top e end quit a previous q p next n', 'tags': '', 'text': '\n Manipulate the text paging. Catch no-input with aliases.\n '}¶
+search_index_entry = {'aliases': 'next previous abort p t end e a top q n quit', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' next previous abort p t end e a top q n quit', 'tags': '', 'text': '\n Manipulate the text paging. Catch no-input with aliases.\n '}¶
diff --git a/docs/latest/api/evennia.utils.search.html b/docs/latest/api/evennia.utils.search.html
index 71a1f3c044..20692f1a75 100644
--- a/docs/latest/api/evennia.utils.search.html
+++ b/docs/latest/api/evennia.utils.search.html
@@ -134,151 +134,182 @@ there is only one match) unless noted otherwise.
> match = Object.objects.get_object_with_account(…)
searchdata (str or Object) – The entity to match for. This is
-usually a key string but may also be an object itself.
-By default (if no attribute_name is set), this will
-search object.key and object.aliases.
-Can also be on the form #dbref, which will (if
-exact=True) be matched against primary key.
-
attribute_name (str) – Use this named Attribute to
-match searchdata against, instead of the defaults. If
-this is the name of a database field (with or without
-the db_ prefix), that will be matched too.
-
typeclass (str or TypeClass) – restrict matches to objects
-having this typeclass. This will help speed up global
-searches.
-
candidates (list) – 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 searched.
-
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 candidates keyword to limit the
-number of possibilities. This value has no meaning if
-searching for attributes/properties.
-
use_dbref (bool) – If False, bypass direct lookup of a string
-on the form #dbref and treat it like any string.
-
tags (list) – A list of tuples (tagkey, tagcategory) where the
-matched object must have _all_ tags in order to be considered
-a match.
+
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 (QuerySet) – Matching objects
+
matches (list) – List of Objects matching the search criteria.
ostring (str or int) – A key string or database id.
-
exact (bool, optional) – Only valid for string matches. If
-True, requires exact (non-case-sensitive) match,
-otherwise also match also keys containing the ostring
-(non-case-sensitive fuzzy match).
-
typeclass (str or Typeclass, optional) – Limit the search only to
-accounts of this typeclass.
+
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
-
Queryset – A queryset (an iterable) with 0, 1 or more matches.
+
matches (list) – List of Accounts matching the search criteria.
ostring (str) – Search criterion - a script dbef or key.
-
obj (Object, optional) – Limit search to scripts defined on
-this object
-
only_timed (bool) – Limit search only to scripts that run
-on a timer.
-
typeclass (class or str) – Typeclass or path to typeclass.
+
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
-
Queryset – An iterable with 0, 1 or more results.
+
matches (list) – List of Scripts matching the search criteria.
sender (Object, Account or Script, optional) – Get messages sent by a particular sender.
-
receiver (Object, Account or Channel, optional) – Get messages
-received by a certain account,object or channel
-
freetext (str) – 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.
-
dbref (int) – The exact database id of the message. This will override
-all other search criteria since it’s unique and
-always gives only one match.
+
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
-
Queryset – Iterable with 0, 1 or more matches.
+
matches (list) – List of Messages matching the search criteria.
ostring (str) – The key or database id of the channel.
-
exact (bool, optional) – Require an exact (but not
-case sensitive) match.
+
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
-
Queryset – Iterable with 0, 1 or more matches.
+
matches (list) – List of Channels matching the search criteria.
category (str) – Limit the search to a particular help topic
+
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
-
Queryset – An iterable with 0, 1 or more matches.
+
matches (list) – List of HelpEntries matching the search criteria.
diff --git a/docs/latest/api/evennia.web.website.views.characters.html b/docs/latest/api/evennia.web.website.views.characters.html
index e0b2de1a0a..a8a673d0f7 100644
--- a/docs/latest/api/evennia.web.website.views.characters.html
+++ b/docs/latest/api/evennia.web.website.views.characters.html
@@ -303,7 +303,7 @@ list of all characters the user may access.
DeleteView is used for deleting objects, be they Accounts, Characters or
diff --git a/docs/latest/api/evennia.web.website.views.objects.html b/docs/latest/api/evennia.web.website.views.objects.html
index bfaf498a9b..b0c84771a3 100644
--- a/docs/latest/api/evennia.web.website.views.objects.html
+++ b/docs/latest/api/evennia.web.website.views.objects.html
@@ -187,7 +187,7 @@ default title for the page.
Any view you write that deals with deleting a specific object will want to
diff --git a/docs/latest/genindex.html b/docs/latest/genindex.html
index 3de25c362e..a11aa16bc1 100644
--- a/docs/latest/genindex.html
+++ b/docs/latest/genindex.html
@@ -22272,13 +22272,19 @@