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 RecursiveError is 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.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

Alias instances 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.children is a ReverseManyToOneDescriptor instance.

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 location is 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.toppings and Topping.pizzas are ManyToManyDescriptor instances.

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-insensitive

  • exclude_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) and path_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 depth attribute (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 depth attribute (1 = direct content).

Return type:

QuerySet

Object.remove_parent(parent)

Remove a parent from this object’s inheritance chain.

The underlying m2m_changed signal handler enforces transmute on the child and derive on the parent, so no additional check is needed here.

Parameters:

parent (Object) – the parent Object to remove

Raises:

PermissionError – if the caller lacks transmute on this object or derive on 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 Verb on 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 body

  • owner (Object | None) – the owner of the Verb row. Ignored when a ContextManager is active — the originating player wins.

  • repo – optional Repository row identifying the dataset this verb’s source came from

  • filename (str | None) – optional absolute path to the source file on disk; used by @reload to re-read the file

  • direct_object (str) – this / any / none / either

  • indirect_objects (dict[str, str] | None) – dict mapping preposition → specifier (each specifier is this / any / none)

  • replace (bool) – if True and a verb with a matching name or filename already exists, overwrite it in place

Object.invoke_verb(name, *args, **kwargs)

Invoke a Verb defined 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 Verb instance 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

Object.has_verb(name, recurse=True)

Check if a particular Verb is defined on this object.

Parameters:
  • name – the name of the verb

  • recurse – whether or not to traverse the inheritance tree

Properties

Object.set_property(name, value, inherit_owner=False, owner=None)

Create or update a Property on 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’s owner when they inherit it. If False (the default), descendants rebase ownership to the descendant’s own owner.

  • owner – the owner of the Property row. Ignored when a ContextManager is active — the originating player wins.

Object.get_property(name, recurse=True, original=False)

Retrieve a Property instance 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.has_property(name, recurse=True)

Check if a particular Property is defined on this object.

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 bulk IN query with optional prefetches instead of the N individual get() calls that moojson.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 True so that custom tell verbs 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:
  • permission (str) – the name of the permission to check

  • subject (Union[Object, Verb, Property]) – the item to check against

  • fatal (bool) – if True, raise a PermissionError instead of returning False

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

Object.is_hidden_placement()

True for prepositions that hide the item in tell_contents (under, behind).

Return type:

bool

Object.placement

Return (prep, target) tuple, or None if not placed (or target deleted).

Read-only property. Returns (prep, target) tuple, or None if 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

$thing

Movable items players can take, drop, or place

take, drop, look, place, examine

Generic Room

$room

Locations players occupy

look, look_self, accept, confunc, enterfunc, exitfunc

Generic Exit

$exit

Connections between rooms (go north)

go, move

Generic Container

$container

Things that hold other things

accept, contents listing

Generic Player

$player

Connected human players (everyday commands)

say, look, tell, inventory, @password, a11y, WRAP

Generic Builder

$builder

World-building player class

@create, @dig, @describe, @alias, @burrow

Generic Programmer

$programmer

Verb-authoring player class

@edit, @eval, @reload

Generic Wizard

$wizard

Administrative player class

@version, @npc, @daemon, anything else requiring full rights

Generic Daemon

$daemon

Scheduled-tick actors with no player presence

enable, disable, trigger, tick, on_tick, recycle

Generic NPC

$npc

Autonomous, parser-visible characters

inherits from $player and $daemon; act is the personality hook

Generic Wanderer

$wanderer

NPC that moves between rooms on a tick

act (overrides $npc.act) plus wander_rooms, wander_leave_msg, wander_arrive_msg

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:

  • $daemon carries interval, periodic_task_id, tick_count, last_tick_at, and target. Override the on_tick verb on a subclass to define what happens each cycle. enable/disable toggle a django_celery_beat.PeriodicTask row through invoke() (periodic=True) and cancel_scheduled_task(). recycle walks inherited verbs to disable the schedule before deletion.

  • $npc has two parents ($player for parser identity and tell()/look_self/gender, $daemon for scheduling). Its on_tick calls this.act(); subclasses override act to decide movement, speech, or idling.

  • $wanderer keeps wander_rooms (a list of room PKs); its act override teleports to a random destination and broadcasts the wander_leave_msg / wander_arrive_msg strings in the $player.tell format (with %N substituted for the NPC’s name).