Note

You are reading an old version of the Evennia documentation. The latest version is here

.

# Source code for evennia.web.admin.objects

    #
    # This sets up how models are displayed
    # in the web admin interface.
    #
    from django import forms
    from django.conf import settings
    from django.contrib import admin, messages
    from django.contrib.admin.utils import flatten_fieldsets
    from django.contrib.admin.widgets import ForeignKeyRawIdWidget
    from django.http import HttpResponseRedirect
    from django.urls import path, reverse
    from django.utils.html import format_html
    from django.utils.translation import gettext as _

    from evennia.accounts.models import AccountDB
    from evennia.objects.models import ObjectDB

    from . import utils as adminutils
    from .attributes import AttributeInline
    from .tags import TagInline

    class ObjectAttributeInline(AttributeInline):
        """
        Defines inline descriptions of Attributes (experimental)

        """

        model = ObjectDB.db_attributes.through
        related_field = "objectdb"

    class ObjectTagInline(TagInline):
        """
        Defines inline descriptions of Tags (experimental)

        """

        model = ObjectDB.db_tags.through
        related_field = "objectdb"

    class ObjectCreateForm(forms.ModelForm):
        """
        This form details the look of the fields.

        """

        class Meta(object):
            model = ObjectDB
            fields = "__all__"

        db_key = forms.CharField(
            label="Name/Key",
            widget=forms.TextInput(attrs={"size": "78"}),
            help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. "
            "If creating a Character, check so the name is unique among characters!",
        )
        db_typeclass_path = forms.ChoiceField(
            label="Typeclass",
            initial={settings.BASE_OBJECT_TYPECLASS: settings.BASE_OBJECT_TYPECLASS},
            help_text="This is the Python-path to the class implementing the actual functionality. "
            f"<BR>If you are creating a Character you usually need <B>{settings.BASE_CHARACTER_TYPECLASS}</B> "
            "or a subclass of that. <BR>If your custom class is not found in the list, it may not be imported "
            "into Evennia yet.",
            choices=lambda: adminutils.get_and_load_typeclasses(parent=ObjectDB),
        )

        db_lock_storage = forms.CharField(
            label="Locks",
            required=False,
            widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}),
            help_text="In-game lock definition string. If not given, defaults will be used. "
            "This string should be on the form "
            "<i>type:lockfunction(args);type2:lockfunction2(args);...",
        )
        db_cmdset_storage = forms.CharField(
            label="CmdSet",
            initial="",
            required=False,
            widget=forms.TextInput(attrs={"size": "78"}),
        )

        # This is not working well because it will not properly allow an empty choice, and will
        # also not work well for comma-separated storage without more work. Notably, it's also
        # a bit hard to visualize.
        # db_cmdset_storage = forms.MultipleChoiceField(
        #     label="CmdSet",
        #     required=False,
        #     choices=adminutils.get_and_load_typeclasses(parent=ObjectDB))

        db_location = forms.ModelChoiceField(
            ObjectDB.objects.all(),
            label="Location",
            required=False,
            widget=ForeignKeyRawIdWidget(
                ObjectDB._meta.get_field("db_location").remote_field, admin.site
            ),
            help_text="The (current) in-game location.<BR>"
            "Usually a Room but can be<BR>"
            "empty for un-puppeted Characters.",
        )
        db_home = forms.ModelChoiceField(
            ObjectDB.objects.all(),
            label="Home",
            required=False,
            widget=ForeignKeyRawIdWidget(
                ObjectDB._meta.get_field("db_location").remote_field, admin.site
            ),
            help_text="Fallback in-game location.<BR>"
            "All objects should usually have<BR>"
            "a home location.",
        )
        db_destination = forms.ModelChoiceField(
            ObjectDB.objects.all(),
            label="Destination",
            required=False,
            widget=ForeignKeyRawIdWidget(
                ObjectDB._meta.get_field("db_destination").remote_field, admin.site
            ),
            help_text="Only used by Exits.",
        )

        def __init__(self, *args, **kwargs):
            """
            Tweak some fields dynamically.

            """
            super().__init__(*args, **kwargs)

            # set default home
            home_id = str(settings.DEFAULT_HOME)
            home_id = home_id[1:] if home_id.startswith("#") else home_id
            default_home = ObjectDB.objects.filter(id=home_id)
            if default_home:
                default_home = default_home[0]
            self.fields["db_home"].initial = default_home
            self.fields["db_location"].initial = default_home

            # better help text for cmdset_storage
            char_cmdset = settings.CMDSET_CHARACTER
            account_cmdset = settings.CMDSET_ACCOUNT
            self.fields["db_cmdset_storage"].help_text = (
                "Path to Command-set path. Most non-character objects don't need a cmdset"
                " and can leave this field blank. Default cmdset-path<BR> for Characters "
                f"is <strong>{char_cmdset}</strong> ."
            )

    class ObjectEditForm(ObjectCreateForm):
        """
        Form used for editing. Extends the create one with more fields

        """

        class Meta:
            model = ObjectDB
            fields = "__all__"

        db_account = forms.ModelChoiceField(
            AccountDB.objects.all(),
            label="Puppeting Account",
            required=False,
            widget=ForeignKeyRawIdWidget(
                ObjectDB._meta.get_field("db_account").remote_field, admin.site
            ),
            help_text="An Account puppeting this Object (if any).<BR>Note that when a user logs "
            "off/unpuppets, this<BR>field will be empty again. This is normal.",
        )

    @admin.register(ObjectDB)
    class ObjectAdmin(admin.ModelAdmin):
        """
        Describes the admin page for Objects.

        """

        inlines = [ObjectTagInline, ObjectAttributeInline]
        list_display = (
            "id",
            "db_key",
            "db_typeclass_path",
            "db_location",
            "db_destination",
            "db_account",
            "db_date_created",
        )
        list_display_links = ("id", "db_key")
        ordering = ["-db_date_created", "-id"]
        search_fields = [
            "=id",
            "^db_key",
            "db_typeclass_path",
            "^db_account__db_key",
            "^db_location__db_key",
        ]
        raw_id_fields = ("db_destination", "db_location", "db_home", "db_account")
        readonly_fields = ("serialized_string", "link_button")

        save_as = True
        save_on_top = True
        list_select_related = True
        view_on_site = False
        list_filter = ("db_typeclass_path",)

        # editing fields setup

        form = ObjectEditForm
        fieldsets = (
            (
                None,
                {
                    "fields": (
                        ("db_key", "db_typeclass_path"),
                        ("db_location", "db_home", "db_destination"),
                        ("db_account", "link_button"),
                        "db_cmdset_storage",
                        "db_lock_storage",
                        "serialized_string",
                    )
                },
            ),
        )

        add_form = ObjectCreateForm
        add_fieldsets = (
            (
                None,
                {
                    "fields": (
                        ("db_key", "db_typeclass_path"),
                        ("db_location", "db_home", "db_destination"),
                        "db_cmdset_storage",
                    )
                },
            ),
        )

        def serialized_string(self, obj):
            """
            Get the serialized version of the object.

            """
            from evennia.utils import dbserialize

            return str(dbserialize.pack_dbobj(obj))

        serialized_string.help_text = (
            "Copy & paste this string into an Attribute's `value` field to store this object there."
        )

        def get_fieldsets(self, request, obj=None):
            """
            Return fieldsets.

            Args:
                request (Request): Incoming request.
                obj (Object, optional): Database object.
            """
            if not obj:
                return self.add_fieldsets
            return super().get_fieldsets(request, obj)

        def get_form(self, request, obj=None, **kwargs):
            """
            Use special form during creation.

            Args:
                request (Request): Incoming request.
                obj (Object, optional): Database object.

            """
            help_texts = kwargs.get("help_texts", {})
            help_texts["serialized_string"] = self.serialized_string.help_text
            kwargs["help_texts"] = help_texts

            defaults = {}
            if obj is None:
                defaults.update(
                    {"form": self.add_form, "fields": flatten_fieldsets(self.add_fieldsets)}
                )
            defaults.update(kwargs)
            return super().get_form(request, obj, **defaults)

        def get_urls(self):
            urls = super().get_urls()
            custom_urls = [
                path(
                    "account-object-link/<int:pk>",
                    self.admin_site.admin_view(self.link_object_to_account),
                    name="object-account-link",
                )
            ]
            return custom_urls + urls

        def link_button(self, obj):
            return format_html(
                '<a class="button" href="{}">Link to Account</a>&nbsp;',
                reverse("admin:object-account-link", args=[obj.pk]),
            )

        link_button.short_description = "Create attrs/locks for puppeting"
        link_button.allow_tags = True

        def link_object_to_account(self, request, object_id):
            """
            Link object and account when pressing the button.

            This will:

            - Set account.db._last_puppet to this object
            - Add object to account.characters
            - Change object locks to allow puppeting by account

            """
            obj = self.get_object(request, object_id)
            account = obj.db_account

            if account:
                account.db._last_puppet = obj
                account.characters.add(obj)
                if not obj.access(account, "puppet"):
                    lock = obj.locks.get("puppet")
                    lock += f" or pid({account.id})"
                    obj.locks.add(lock)
                self.message_user(
                    request,
                    "Did the following (where possible): "
                    f"Set Account.db._last_puppet = {obj}, "
                    f"Added {obj} to Account.characters list, "
                    f"Added 'puppet:pid({account.id})' lock to {obj}.",
                )
            else:
                self.message_user(
                    request,
                    "Account must be connected for this action "
                    "(set Puppeting Account and save this page first).",
                    level=messages.ERROR,
                )

            # stay on the same page
            return HttpResponseRedirect(reverse("admin:objects_objectdb_change", args=[obj.pk]))

        def save_model(self, request, obj, form, change):
            """
            Model-save hook.

            Args:
                request (Request): Incoming request.
                obj (Object): Database object.
                form (Form): Form instance.
                change (bool): If this is a change or a new object.

            """
            if not change:
                # adding a new object
                # have to call init with typeclass passed to it
                obj.set_class_from_typeclass(typeclass_path=obj.db_typeclass_path)
                obj.save()
                obj.basetype_setup()
                obj.basetype_posthook_setup()
                obj.at_object_creation()
            else:
                obj.save()
                obj.at_init()

        def response_add(self, request, obj, post_url_continue=None):
            from django.http import HttpResponseRedirect
            from django.urls import reverse

            return HttpResponseRedirect(reverse("admin:objects_objectdb_change", args=[obj.id]))

Note

You are reading an old version of the Evennia documentation. The latest version is here

.