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


[docs]class ObjectAttributeInline(AttributeInline): """ Defines inline descriptions of Attributes (experimental) """ model = ObjectDB.db_attributes.through related_field = "objectdb"
[docs]class ObjectTagInline(TagInline): """ Defines inline descriptions of Tags (experimental) """ model = ObjectDB.db_tags.through related_field = "objectdb"
[docs]class ObjectCreateForm(forms.ModelForm): """ This form details the look of the fields. """
[docs] 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.", )
[docs] 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> ." )
[docs]class ObjectEditForm(ObjectCreateForm): """ Form used for editing. Extends the create one with more fields """
[docs] 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.", )
[docs]@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", ) }, ), )
[docs] 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." )
[docs] 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)
[docs] 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)
[docs] 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
link_button.short_description = "Create attrs/locks for puppeting" link_button.allow_tags = True
[docs] 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()
[docs] 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]))