Objects in the DjangoMOO Database
Object is a Django model representing every entity in the world:
rooms, players, items, exits, and the system object itself. Each
Object has a primary key (the LambdaMOO-style #N identifier), a name,
an owner, optional parents and a location, and arbitrary properties
and verbs.
This page covers the model’s intrinsic attributes, public methods, and placement API. For the conceptual model of how parent inheritance feeds into verb and property lookup, see Caching.
Identity and lifecycle
Object PKs are assigned at creation and never reused. A new Object
exists in the database only after create() (or Object.objects.create()
from non-sandbox code). @recycle removes an Object — its PK will not be
reused.
obj.save() is required after changing intrinsic fields (name,
unique_name, obvious, owner). set_property() saves its own
row; you do not need to call save() after it.
Object.delete() (which @recycle calls) walks the full inherited
verb chain looking for a recycle verb and runs the first match.
That means a subclass’s recycle fires automatically — $daemon,
$npc, and $wanderer all rely on this to disable their scheduled
ticks and drop their anonymous Player rows before the row is
removed.
Fundamental attributes
- Object.pk
The unique identifying number of this Object.
- Object.name
The canonical name of the object
- Object.unique_name
If True, this object is the only object with this name
- Object.obvious
This object should be obvious among a group. The meaning of this value is database-dependent.
- Object.owner
The owner of this object. Changes require entrust permission.
- Object.parents
The parents of this object. Changes require derive and transmute permissions, respectively.
from moo.core import context, lookup # in the default DB, all wizards inherit from this Object wizard_class = lookup("wizard class") # Changes to ManyToMany fields like this are automatically saved context.caller.parents.add(wizard_class)
- Object.location
The location of this object. When changing, this kicks off some other verbs:
If where is the new object, then the verb-call where.accept(self) is performed before any movement takes place. If the verb returns a false value and the programmer is not a wizard, then where is considered to have refused entrance to self and raises
PermissionError. If where does not define an accept verb, then it is treated as if it defined one that always returned false.If moving what into self would create a loop in the containment hierarchy (i.e., what would contain itself, even indirectly), then
RecursiveErroris raised instead.Let old be the location of self before it was moved. If old is a valid object, then the verb-call old.exitfunc(self) is performed and its result is ignored; it is not an error if old does not define a verb named exitfunc.
Finally, if where is still the location of self, then the verb-call where.enterfunc(self) is performed and its result is ignored; again, it is not an error if where does not define a verb named enterfunc.
- Object.aliases
Accessor to the related objects manager on the reverse side of a many-to-one relation.
In the example:
class Child(Model): parent = ForeignKey(Parent, related_name='children')
Parent.childrenis aReverseManyToOneDescriptorinstance.Most of the implementation is delegated to a dynamically defined manager class built by
create_forward_many_to_many_manager()defined below.Aliasinstances giving this object additional names.
- Object.contents
Accessor to the related objects manager on the reverse side of a many-to-one relation.
In the example:
class Child(Model): parent = ForeignKey(Parent, related_name='children')
Parent.childrenis aReverseManyToOneDescriptorinstance.Most of the implementation is delegated to a dynamically defined manager class built by
create_forward_many_to_many_manager()defined below.QuerySet of objects whose
locationis this object — i.e. what’s inside this room or this container.
- Object.children
Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.
In the example:
class Pizza(Model): toppings = ManyToManyField(Topping, related_name='pizzas')
Pizza.toppingsandTopping.pizzasareManyToManyDescriptorinstances.Most of the implementation is delegated to a dynamically defined manager class built by
create_forward_many_to_many_manager()defined below.QuerySet of objects that have this object as a parent.
- Object.placement_prep
The preposition describing where this object is placed (e.g. “on”, “under”). None if not placed.
- Object.placement_target
The object this is placed on/under/behind. SET_NULL on target deletion.
Inheritance
Objects can have multiple parents. Property and verb lookup walks the
parent chain in depth-then-weight order, materialised through the
AncestorCache denormalised table for cheap dispatch. See
Caching for the lookup architecture and the relationship-weight
semantics.
Methods
Identity and traversal
- Object.find(name, exclude_hidden_placement=False)
Find contents by the given name or alias.
- Parameters:
name (
str) – the name or alias to search for, case-insensitiveexclude_hidden_placement (
bool) – if True, exclude objects whose placement_prep is in HIDDEN_PLACEMENT_PREPS (i.e., placed under or behind something). Used by the parser to make hidden objects unfindable by name.
- Return type:
QuerySet
- Object.contains(obj)
Check if this object contains the given object anywhere in its content tree.
- Parameters:
obj (
Object) – the object to search for- Return type:
bool- Returns:
True if obj is found in the content tree of this object
- Object.is_a(obj)
Check if this object is a child of the provided object.
- Parameters:
obj (
Object) – the potential parent object- Return type:
bool- Returns:
True if this object is a child of obj
- Object.is_named(name)
Check if this object has a name or alias that matches the given name.
- Return type:
bool
Hierarchy
- Object.get_ancestors()
Get the ancestor tree for this object as a QuerySet ordered shallowest-first. Each Object is annotated with
depth(1 = direct parent) andpath_weight.- Return type:
QuerySet
- Object.get_descendents()
Get the descendent tree for this object as a QuerySet ordered shallowest-first. Each Object is annotated with a
depthattribute (1 = direct child).- Return type:
QuerySet
- Object.get_contents()
Get the content tree for this object as a QuerySet ordered shallowest-first. Each Object is annotated with a
depthattribute (1 = direct content).- Return type:
QuerySet
- Object.remove_parent(parent)
Remove a parent from this object’s inheritance chain.
The underlying
m2m_changedsignal handler enforcestransmuteon the child andderiveon the parent, so no additional check is needed here.- Parameters:
parent (
Object) – the parent Object to remove- Raises:
PermissionError – if the caller lacks
transmuteon this object orderiveon the parent.
Verbs
- Object.add_verb(*names, code=None, owner=None, repo=None, filename=None, direct_object='none', indirect_objects=None, replace=False)
Create or replace a
Verbon this object.- Parameters:
names (
str) – one or more names/aliases for the verb (passed as positional args, e.g.add_verb("@reload", "reload_batch", ...))code (
str|None) – the Python source for the verb bodyowner (
Object|None) – the owner of the Verb row. Ignored when aContextManageris active — the originating player wins.repo – optional
Repositoryrow identifying the dataset this verb’s source came fromfilename (
str|None) – optional absolute path to the source file on disk; used by@reloadto re-read the filedirect_object (
str) –this/any/none/eitherindirect_objects (
dict[str,str] |None) – dict mapping preposition → specifier (each specifier isthis/any/none)replace (
bool) – ifTrueand a verb with a matching name or filename already exists, overwrite it in place
- Object.invoke_verb(name, *args, **kwargs)
Invoke a
Verbdefined on the given object, traversing the inheritance tree until it’s found.- Parameters:
name – the name of the verb
args – positional arguments for the verb
kwargs – keyword arguments for the verb
- Object.get_verb(name, recurse=True, allow_ambiguous=False, return_first=True)
Retrieve a specific
Verbinstance defined on this Object.- Parameters:
name – the name of the verb
recurse – whether or not to traverse the inheritance tree
return_first – if True, return the first matching verb, otherwise return all matching verbs
Properties
- Object.set_property(name, value, inherit_owner=False, owner=None)
Create or update a
Propertyon this object.- Parameters:
name – the property name (unique per object)
value – the property value; serialised via moojson
inherit_owner – if
True, descendants keep this property’sownerwhen they inherit it. IfFalse(the default), descendants rebase ownership to the descendant’s own owner.owner – the owner of the Property row. Ignored when a
ContextManageris active — the originating player wins.
- Object.get_property(name, recurse=True, original=False)
Retrieve a
Propertyinstance defined on this Object.- Parameters:
name – the name of the verb
recurse – whether or not to traverse the inheritance tree
original – if True, return the whole Property object, not just its value
- Object.get_property_objects(name, prefetch_related=None, select_related=None)
Like
get_property(), but when the stored value is a list of Objects, returns them via a single bulkINquery with optional prefetches instead of the N individualget()calls thatmoojson.loads()would issue.Falls back to
get_property()for non-list or non-Object values.- Parameters:
name – the property name
prefetch_related – iterable of relation paths to prefetch on the result
select_related – iterable of FK paths to JOIN on the result
Roles and permissions
- Object.is_player()
Check if this object is a player avatar.
- Return type:
bool
- Object.is_wizard()
Check if this object is a wizard player avatar.
- Return type:
bool
- Object.is_connected()
Check if this object is a player avatar that is currently connected.
Non-player objects always return
Trueso that customtellverbs on room fixtures and other objects continue to receive messages normally.For player avatars, connection state is tracked via a Redis cache key (
moo:connected:{user_pk}) that is set by the SSH shell at login and deleted at logout. This is cross-process and has no timing window.- Return type:
bool
- Object.owns(subject)
Convenience method to check if the subject is owned by self
- Return type:
bool
- Object.is_allowed(permission, subject, fatal=False)
Check if this object is allowed to perform an action on an object.
- Parameters:
- Raises:
PermissionError – if permission is denied and fatal is set to True
- Return type:
bool
can_caller(permission) is also available on every Object (inherited
from AccessibleMixin); it answers “would the current
context.caller succeed in performing <permission> on this object?”
without actually performing the operation. See
How Permissions Work in Verbs for when to reach for it (and when not
to — most verb code should just attempt the operation and let
AccessError propagate).
Placement
Objects can be placed in a spatial relationship to another object in the same room. Placement is stored as two fields:
placement_prep— a preposition string ("on","under","behind","before","beside","over").placement_target— the Object the placement is relative to.
The full set of valid prepositions is PLACEMENT_PREPS, and the
hidden subset ("under", "behind") is HIDDEN_PLACEMENT_PREPS.
Both are importable from moo.sdk. Hidden-placement objects are
invisible in the room contents listing and unfindable by name through
the parser; they can only be revealed by look under <target> or
look behind <target>.
Visible placements (on, before, beside, over) appear grouped
under their surface in the room contents:
On the desk: a coffee cup.
Placement is cleared automatically when an object is taken, dropped,
or moved. If the placement target is deleted, the database SET_NULL
clears the placement_target FK and Object.delete() clears the
dangling placement_prep on every placed child.
Placement API
- Object.set_placement(prep, target)
Set placement prep and target atomically.
- Return type:
None
- Object.clear_placement()
Remove placement without touching the obvious field.
- Return type:
None
- Object.is_placed()
True if this object has an active placement.
- Return type:
bool
True for prepositions that hide the item in tell_contents (under, behind).
- Return type:
bool
- Object.placement
Return
(prep, target)tuple, orNoneif not placed (or target deleted).Read-only property. Returns
(prep, target)tuple, orNoneif not placed.
Restricting valid placements
Set the surface_types property on a target to limit which
prepositions it accepts:
desk.set_property("surface_types", ["on", "beside"])
# "place book on desk" succeeds; "place book under desk" fails.
If surface_types is absent, all placement prepositions are accepted.
Players and avatars
A Player row links a Django User to its avatar Object via
Player.avatar. The Player record also carries the wizard boolean
that gates is_wizard() checks. The user-facing player is the avatar
Object — Player itself is plumbing for authentication and role.
To find an avatar from a username, query the Player model from
non-sandbox code, or call lookup("<player name>") from verb code.
Built-in classes
The default bootstrap installs a small hierarchy of generic classes
as direct children of $root_class. Every game object in default
inherits from one of these, and your own creations should too. Each
class is also stored as a property on the System Object (_) for
convenient $name shorthand in verb shebangs (--on $thing).
Class |
System alias |
Use for |
Notable verbs |
|---|---|---|---|
Generic Thing |
|
Movable items players can take, drop, or place |
|
Generic Room |
|
Locations players occupy |
|
Generic Exit |
|
Connections between rooms ( |
|
Generic Container |
|
Things that hold other things |
|
Generic Player |
|
Connected human players (everyday commands) |
|
Generic Builder |
|
World-building player class |
|
Generic Programmer |
|
Verb-authoring player class |
|
Generic Wizard |
|
Administrative player class |
|
Generic Daemon |
|
Scheduled-tick actors with no player presence |
|
Generic NPC |
|
Autonomous, parser-visible characters |
inherits from |
Generic Wanderer |
|
NPC that moves between rooms on a tick |
|
The player-class chain ($player → $builder → $programmer →
$wizard) is cumulative — $wizard inherits every verb attached to
$player. Place each new player-facing verb on the lowest class that
should be allowed to use it.
$daemon, $npc, and $wanderer are covered in detail in
NPCs and Daemons. The relevant lifecycle for verb
authors:
$daemoncarriesinterval,periodic_task_id,tick_count,last_tick_at, andtarget. Override theon_tickverb on a subclass to define what happens each cycle.enable/disabletoggle adjango_celery_beat.PeriodicTaskrow throughinvoke()(periodic=True)andcancel_scheduled_task().recyclewalks inherited verbs to disable the schedule before deletion.$npchas two parents ($playerfor parser identity andtell()/look_self/gender,$daemonfor scheduling). Itson_tickcallsthis.act(); subclasses overrideactto decide movement, speech, or idling.$wandererkeepswander_rooms(a list of room PKs); itsactoverride teleports to a random destination and broadcasts thewander_leave_msg/wander_arrive_msgstrings in the$player.tellformat (with%Nsubstituted for the NPC’s name).