Add account-object link button to admin

This commit is contained in:
Griatch 2021-05-20 22:55:26 +02:00
parent 14968e4b42
commit a7f1e24c9c
4 changed files with 167 additions and 46 deletions

View file

@ -6,7 +6,7 @@ from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import admin, messages from django.contrib import admin, messages
from django.contrib.admin.options import IS_POPUP_VAR 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.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.contrib.auth.forms import UserChangeForm, UserCreationForm from django.contrib.auth.forms import UserChangeForm, UserCreationForm
@ -236,6 +236,39 @@ class AccountAttributeInline(AttributeInline):
model = AccountDB.db_attributes.through model = AccountDB.db_attributes.through
related_field = "accountdb" 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) @admin.register(AccountDB)
class AccountAdmin(BaseUserAdmin): class AccountAdmin(BaseUserAdmin):
@ -246,8 +279,8 @@ class AccountAdmin(BaseUserAdmin):
list_display = ("username", "email", "is_staff", "is_superuser") list_display = ("username", "email", "is_staff", "is_superuser")
form = AccountChangeForm form = AccountChangeForm
add_form = AccountCreationForm add_form = AccountCreationForm
inlines = [AccountTagInline, AccountAttributeInline] inlines = [AccountTagInline, AccountAttributeInline, ObjectPuppetInline]
readonly_fields = ["db_date_created", "serialized_string"] readonly_fields = ["db_date_created", "serialized_string", "link_button"]
view_on_site = False view_on_site = False
fieldsets = ( fieldsets = (
( (

View file

@ -73,6 +73,8 @@ class MsgAdmin(admin.ModelAdmin):
save_as = True save_as = True
save_on_top = True save_on_top = True
list_select_related = True list_select_related = True
view_on_site = False
raw_id_fields = ( raw_id_fields = (
"db_date_created", "db_sender_accounts", "db_date_created", "db_sender_accounts",
"db_sender_objects", "db_sender_scripts", "db_sender_objects", "db_sender_scripts",

View file

@ -2,11 +2,16 @@
# This sets up how models are displayed # This sets up how models are displayed
# in the web admin interface. # in the web admin interface.
# #
from django import forms
from django.conf import settings 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.utils import flatten_fieldsets
from django.contrib.admin.widgets import ForeignKeyRawIdWidget from django.contrib.admin.widgets import ForeignKeyRawIdWidget
from django.utils.html import format_html
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from evennia.objects.models import ObjectDB from evennia.objects.models import ObjectDB
@ -68,26 +73,66 @@ class ObjectCreateForm(forms.ModelForm):
"This string should be on the form " "This string should be on the form "
"<i>type:lockfunction(args);type2:lockfunction2(args);...", "<i>type:lockfunction(args);type2:lockfunction2(args);...",
) )
db_cmdset_storage = forms.CharField( db_cmdset_storage = forms.CharField(
label="CmdSet", label="CmdSet",
initial="", initial="",
required=False, required=False,
widget=forms.TextInput(attrs={"size": "78"}), widget=forms.TextInput(attrs={"size": "78"}),
help_text="Most non-character objects don't need a cmdset"
" and can leave this field blank.",
) )
db_location = forms.ModelChoiceField(
db_account = forms.ModelChoiceField( ObjectDB.objects.all(),
AccountDB.objects.all(), label="Location",
label="Controlling Account",
required=False, required=False,
widget=ForeignKeyRawIdWidget( widget=ForeignKeyRawIdWidget(
ObjectDB._meta.get_field('db_account').remote_field, admin.site), ObjectDB._meta.get_field('db_location').remote_field, admin.site),
help_text="Only needed for characters in MULTISESSION_MODE=1 or 2." 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. Some common cmdset-paths<BR> are "
f"<strong>{char_cmdset}</strong> and <strong>{account_cmdset}</strong>"
)
raw_id_fields = ("db_destination", "db_location", "db_home")
class ObjectEditForm(ObjectCreateForm): class ObjectEditForm(ObjectCreateForm):
@ -100,33 +145,16 @@ class ObjectEditForm(ObjectCreateForm):
model = ObjectDB model = ObjectDB
fields = "__all__" fields = "__all__"
db_account = forms.ModelChoiceField(
class ObjectInline(admin.StackedInline): AccountDB.objects.all(),
""" label="Puppeting Account",
Inline creation of Object. required=False,
widget=ForeignKeyRawIdWidget(
""" ObjectDB._meta.get_field('db_account').remote_field, admin.site),
model = ObjectDB help_text="An Account puppeting this Object (if any).<BR>Note that when a user logs "
# template = "admin/accounts/stacked.html" "off/unpuppets, this<BR>field will be empty again. This is normal."
form = ObjectCreateForm
fieldsets = (
(
None,
{
"fields": (
("db_key", "db_typeclass_path"),
("db_location", "db_home", "db_destination", "db_account"),
"db_cmdset_storage",
"db_lock_storage",
)
},
),
) )
extra = 1
max_num = 1
raw_id_fields = ("db_destination", "db_location", "db_home", "db_account")
@admin.register(ObjectDB) @admin.register(ObjectDB)
class ObjectAdmin(admin.ModelAdmin): class ObjectAdmin(admin.ModelAdmin):
@ -141,7 +169,7 @@ class ObjectAdmin(admin.ModelAdmin):
ordering = ["db_account", "db_typeclass_path", "id"] ordering = ["db_account", "db_typeclass_path", "id"]
search_fields = ["=id", "^db_key", "db_typeclass_path", "^db_account__db_key"] search_fields = ["=id", "^db_key", "db_typeclass_path", "^db_account__db_key"]
raw_id_fields = ("db_destination", "db_location", "db_home", "db_account") 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_as = True
save_on_top = True save_on_top = True
@ -158,7 +186,8 @@ class ObjectAdmin(admin.ModelAdmin):
{ {
"fields": ( "fields": (
("db_key", "db_typeclass_path"), ("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_cmdset_storage",
"db_lock_storage", "db_lock_storage",
"serialized_string" "serialized_string"
@ -174,7 +203,7 @@ class ObjectAdmin(admin.ModelAdmin):
{ {
"fields": ( "fields": (
("db_key", "db_typeclass_path"), ("db_key", "db_typeclass_path"),
("db_location", "db_home", "db_destination", "db_account"), ("db_location", "db_home", "db_destination"),
"db_cmdset_storage", "db_cmdset_storage",
) )
}, },
@ -227,6 +256,63 @@ class ObjectAdmin(admin.ModelAdmin):
defaults.update(kwargs) defaults.update(kwargs)
return super().get_form(request, obj, **defaults) return super().get_form(request, obj, **defaults)
def get_urls(self):
urls = super().get_urls()
custom_urls = [
url(
r"^account-object-link/(?P<object_id>.+)/$",
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 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): def save_model(self, request, obj, form, change):
""" """
Model-save hook. Model-save hook.

View file

@ -67,9 +67,9 @@
<p class="card-text"> <p class="card-text">
<h4><a href="{% url "admin:server_serverconfig_changelist" %}">ServerConfig</a></h4> <h4><a href="{% url "admin:server_serverconfig_changelist" %}">ServerConfig</a></h4>
ServerConfigs store variables set by the running server. While possibly ServerConfigs store variables set by the running server. While
interesting for debugging, you should usually not modify these possibly interesting for debugging, you should usually not modify
manually unless you <i>really</i> know what you are doing. For these manually unless you <i>really</i> know what you are doing. For
example, the <i>BASE_*_TYPECLASS</i> fields are stored in order to example, the <i>BASE_*_TYPECLASS</i> fields are stored in order to
auto-update when their setting changes; they must <i>not</i> be auto-update when their setting changes; they must <i>not</i> be
changed manually here. changed manually here.