diff --git a/evennia/web/admin/accounts.py b/evennia/web/admin/accounts.py index 6e949b030e..0a6e450455 100644 --- a/evennia/web/admin/accounts.py +++ b/evennia/web/admin/accounts.py @@ -6,7 +6,7 @@ from django import forms from django.conf import settings from django.contrib import admin, messages from django.contrib.admin.options import IS_POPUP_VAR -from django.contrib.admin.widgets import ForeignKeyRawIdWidget +from django.contrib.admin.widgets import ForeignKeyRawIdWidget, FilteredSelectMultiple from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.utils.translation import gettext as _ from django.contrib.auth.forms import UserChangeForm, UserCreationForm @@ -236,6 +236,39 @@ class AccountAttributeInline(AttributeInline): model = AccountDB.db_attributes.through related_field = "accountdb" +class ObjectPuppetInline(admin.StackedInline): + """ + Inline creation of puppet-Object in Account. + + """ + from .objects import ObjectCreateForm + + model = ObjectDB + view_on_site = False + show_change_link = True + # template = "admin/accounts/stacked.html" + form = ObjectCreateForm + fieldsets = ( + ( + None, + { + "fields": ( + ("db_key", "db_typeclass_path"), + ("db_location", "db_home", "db_destination"), + "db_cmdset_storage", + "db_lock_storage", + ), + "description": "Object currently puppeted by the account (note that this " + "will go away if account logs out or unpuppets)", + }, + ), + ) + + extra = 0 + readonly_fields = ("db_key", "db_typeclass_path", "db_destination", + "db_location", "db_home", "db_account", + "db_cmdset_storage", "db_lock_storage") + @admin.register(AccountDB) class AccountAdmin(BaseUserAdmin): @@ -246,8 +279,8 @@ class AccountAdmin(BaseUserAdmin): list_display = ("username", "email", "is_staff", "is_superuser") form = AccountChangeForm add_form = AccountCreationForm - inlines = [AccountTagInline, AccountAttributeInline] - readonly_fields = ["db_date_created", "serialized_string"] + inlines = [AccountTagInline, AccountAttributeInline, ObjectPuppetInline] + readonly_fields = ["db_date_created", "serialized_string", "link_button"] view_on_site = False fieldsets = ( ( diff --git a/evennia/web/admin/comms.py b/evennia/web/admin/comms.py index 9c2c8e959a..9111bc9ddc 100644 --- a/evennia/web/admin/comms.py +++ b/evennia/web/admin/comms.py @@ -73,6 +73,8 @@ class MsgAdmin(admin.ModelAdmin): save_as = True save_on_top = True list_select_related = True + view_on_site = False + raw_id_fields = ( "db_date_created", "db_sender_accounts", "db_sender_objects", "db_sender_scripts", diff --git a/evennia/web/admin/objects.py b/evennia/web/admin/objects.py index 9c592c748b..b16f045d68 100644 --- a/evennia/web/admin/objects.py +++ b/evennia/web/admin/objects.py @@ -2,11 +2,16 @@ # 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 +from django import forms +from django.urls import reverse +from django.http import HttpResponseRedirect +from django.conf import settings +from django.conf.urls import url +from django.contrib import admin, messages from django.contrib.admin.utils import flatten_fieldsets from django.contrib.admin.widgets import ForeignKeyRawIdWidget +from django.utils.html import format_html from django.utils.translation import gettext as _ from evennia.objects.models import ObjectDB @@ -68,26 +73,66 @@ class ObjectCreateForm(forms.ModelForm): "This string should be on the form " "type:lockfunction(args);type2:lockfunction2(args);...", ) - db_cmdset_storage = forms.CharField( label="CmdSet", initial="", required=False, widget=forms.TextInput(attrs={"size": "78"}), - help_text="Most non-character objects don't need a cmdset" - " and can leave this field blank.", ) - - db_account = forms.ModelChoiceField( - AccountDB.objects.all(), - label="Controlling Account", + db_location = forms.ModelChoiceField( + ObjectDB.objects.all(), + label="Location", required=False, widget=ForeignKeyRawIdWidget( - ObjectDB._meta.get_field('db_account').remote_field, admin.site), - help_text="Only needed for characters in MULTISESSION_MODE=1 or 2." + ObjectDB._meta.get_field('db_location').remote_field, admin.site), + help_text="The (current) in-game location.
" + "Usually a Room but can be
" + "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.
" + "All objects should usually have
" + "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. Some common cmdset-paths
are " + f"{char_cmdset} and {account_cmdset}" + ) - raw_id_fields = ("db_destination", "db_location", "db_home") class ObjectEditForm(ObjectCreateForm): @@ -100,33 +145,16 @@ class ObjectEditForm(ObjectCreateForm): model = ObjectDB fields = "__all__" - -class ObjectInline(admin.StackedInline): - """ - Inline creation of Object. - - """ - model = ObjectDB - # template = "admin/accounts/stacked.html" - form = ObjectCreateForm - fieldsets = ( - ( - None, - { - "fields": ( - ("db_key", "db_typeclass_path"), - ("db_location", "db_home", "db_destination", "db_account"), - "db_cmdset_storage", - "db_lock_storage", - ) - }, - ), + 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).
Note that when a user logs " + "off/unpuppets, this
field will be empty again. This is normal." ) - extra = 1 - max_num = 1 - raw_id_fields = ("db_destination", "db_location", "db_home", "db_account") - @admin.register(ObjectDB) class ObjectAdmin(admin.ModelAdmin): @@ -141,7 +169,7 @@ class ObjectAdmin(admin.ModelAdmin): ordering = ["db_account", "db_typeclass_path", "id"] search_fields = ["=id", "^db_key", "db_typeclass_path", "^db_account__db_key"] raw_id_fields = ("db_destination", "db_location", "db_home", "db_account") - readonly_fields = ("serialized_string", ) + readonly_fields = ("serialized_string", "link_button") save_as = True save_on_top = True @@ -158,7 +186,8 @@ class ObjectAdmin(admin.ModelAdmin): { "fields": ( ("db_key", "db_typeclass_path"), - ("db_location", "db_home", "db_destination", "db_account"), + ("db_location", "db_home", "db_destination"), + ("db_account", "link_button"), "db_cmdset_storage", "db_lock_storage", "serialized_string" @@ -174,7 +203,7 @@ class ObjectAdmin(admin.ModelAdmin): { "fields": ( ("db_key", "db_typeclass_path"), - ("db_location", "db_home", "db_destination", "db_account"), + ("db_location", "db_home", "db_destination"), "db_cmdset_storage", ) }, @@ -227,6 +256,63 @@ class ObjectAdmin(admin.ModelAdmin): defaults.update(kwargs) return super().get_form(request, obj, **defaults) + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + url( + r"^account-object-link/(?P.+)/$", + 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( + 'Link to Account ', + reverse("admin:object-account-link", args=[obj.pk]) + ) + link_button.short_description = "Create puppet links for MULTISESSION_MODE 0/1" + 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.db._playable_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 + if not account.db._playable_characters: + account.db._playable_characters = [] + if obj not in account.db._playable_characters: + account.db._playable_characters.append(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.db._playable_characters list, " + f"Added 'puppet:pid({account.id})' lock to {obj}.") + else: + self.message_user(request, "Account must be connected to set up puppet links " + "(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. diff --git a/evennia/web/templates/admin/frontpage.html b/evennia/web/templates/admin/frontpage.html index b18b7b93cd..d2fafeafda 100644 --- a/evennia/web/templates/admin/frontpage.html +++ b/evennia/web/templates/admin/frontpage.html @@ -67,9 +67,9 @@

ServerConfig

- ServerConfigs store variables set by the running server. While possibly - interesting for debugging, you should usually not modify these - manually unless you really know what you are doing. For + ServerConfigs store variables set by the running server. While + possibly interesting for debugging, you should usually not modify + these manually unless you really know what you are doing. For example, the BASE_*_TYPECLASS fields are stored in order to auto-update when their setting changes; they must not be changed manually here.