mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Start cleaning up the admin
This commit is contained in:
parent
efe3a28343
commit
d11ab6d0ee
10 changed files with 476 additions and 111 deletions
|
|
@ -78,7 +78,7 @@ class Msg(SharedMemoryModel):
|
|||
"accounts.AccountDB",
|
||||
related_name="sender_account_set",
|
||||
blank=True,
|
||||
verbose_name="sender(account)",
|
||||
verbose_name="Senders (Accounts)",
|
||||
db_index=True,
|
||||
)
|
||||
|
||||
|
|
@ -86,14 +86,14 @@ class Msg(SharedMemoryModel):
|
|||
"objects.ObjectDB",
|
||||
related_name="sender_object_set",
|
||||
blank=True,
|
||||
verbose_name="sender(object)",
|
||||
verbose_name="Senders (Objects)",
|
||||
db_index=True,
|
||||
)
|
||||
db_sender_scripts = models.ManyToManyField(
|
||||
"scripts.ScriptDB",
|
||||
related_name="sender_script_set",
|
||||
blank=True,
|
||||
verbose_name="sender(script)",
|
||||
verbose_name="Senders (Scripts)",
|
||||
db_index=True,
|
||||
)
|
||||
db_sender_external = models.CharField(
|
||||
|
|
@ -102,14 +102,15 @@ class Msg(SharedMemoryModel):
|
|||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text="identifier for external sender, for example a sender over an "
|
||||
"IRC connection (i.e. someone who doesn't have an exixtence in-game).",
|
||||
help_text="Identifier for single external sender, for use with senders "
|
||||
"not represented by a regular database model."
|
||||
)
|
||||
|
||||
db_receivers_accounts = models.ManyToManyField(
|
||||
"accounts.AccountDB",
|
||||
related_name="receiver_account_set",
|
||||
blank=True,
|
||||
verbose_name="Receivers (Accounts)",
|
||||
help_text="account receivers",
|
||||
)
|
||||
|
||||
|
|
@ -117,12 +118,14 @@ class Msg(SharedMemoryModel):
|
|||
"objects.ObjectDB",
|
||||
related_name="receiver_object_set",
|
||||
blank=True,
|
||||
verbose_name="Receivers (Objects)",
|
||||
help_text="object receivers",
|
||||
)
|
||||
db_receivers_scripts = models.ManyToManyField(
|
||||
"scripts.ScriptDB",
|
||||
related_name="receiver_script_set",
|
||||
blank=True,
|
||||
verbose_name="Receivers (Scripts)",
|
||||
help_text="script_receivers",
|
||||
)
|
||||
|
||||
|
|
@ -132,8 +135,8 @@ class Msg(SharedMemoryModel):
|
|||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text="identifier for single external receiver, for use with "
|
||||
"receivers without a database existence."
|
||||
help_text="Identifier for single external receiver, for use with recievers "
|
||||
"not represented by a regular database model."
|
||||
)
|
||||
|
||||
# header could be used for meta-info about the message if your system needs
|
||||
|
|
|
|||
|
|
@ -850,8 +850,12 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = False
|
|||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = False
|
||||
|
||||
# Where to find locales (no need to change this, most likely)
|
||||
LOCALE_PATHS = [os.path.join(EVENNIA_DIR, "locale/")]
|
||||
# How to display time stamps in e.g. the admin
|
||||
SHORT_DATETIME_FORMAT = 'Y-m-d H:i:s.u'
|
||||
DATETIME_FORMAT = 'Y-m-d H:i:s' # ISO 8601 but without T and timezone
|
||||
# This should be turned off unless you want to do tests with Django's
|
||||
# development webserver (normally Evennia runs its own server)
|
||||
SERVE_MEDIA = False
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from evennia.accounts.models import AccountDB
|
|||
from evennia.utils import create
|
||||
from .attributes import AttributeInline
|
||||
from .tags import TagInline
|
||||
from . import utils as adminutils
|
||||
|
||||
sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
|
||||
|
||||
|
|
@ -49,6 +50,31 @@ class AccountChangeForm(UserChangeForm):
|
|||
help_text="30 characters or fewer. Letters, spaces, digits and " "@/./+/-/_ only.",
|
||||
)
|
||||
|
||||
db_typeclass_path = forms.ChoiceField(
|
||||
label="Typeclass",
|
||||
help_text="This is the Python-path to the class implementing the actual account functionality. "
|
||||
"You usually don't need to change this from the default.<BR>"
|
||||
"If your custom class is not found here, it may not be imported as part of Evennia's startup.",
|
||||
choices=adminutils.get_and_load_typeclasses(parent=AccountDB),
|
||||
)
|
||||
|
||||
db_lock_storage = forms.CharField(
|
||||
label="Locks",
|
||||
required=False,
|
||||
widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}),
|
||||
help_text="Locks limit access to the entity. Written on form `type:lockdef;type:lockdef..."
|
||||
"<BR>(Permissions (used with the perm() lockfunc) are Tags with the 'permission' type)",
|
||||
)
|
||||
|
||||
db_cmdset_storage = forms.CharField(
|
||||
label="CommandSet",
|
||||
initial=settings.CMDSET_ACCOUNT,
|
||||
widget=forms.TextInput(attrs={"size": "78"}),
|
||||
required=False,
|
||||
help_text="Python path to account cmdset class (set via "
|
||||
"settings.CMDSET_ACCOUNT by default)",
|
||||
)
|
||||
|
||||
def clean_username(self):
|
||||
"""
|
||||
Clean the username and check its existence.
|
||||
|
|
@ -99,7 +125,7 @@ class AccountForm(forms.ModelForm):
|
|||
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
model = AccountDB
|
||||
fields = "__all__"
|
||||
app_label = "accounts"
|
||||
|
|
@ -120,27 +146,21 @@ class AccountForm(forms.ModelForm):
|
|||
"@/./+/-/_ only.",
|
||||
)
|
||||
|
||||
db_typeclass_path = forms.CharField(
|
||||
db_typeclass_path = forms.ChoiceField(
|
||||
label="Typeclass",
|
||||
initial=settings.BASE_ACCOUNT_TYPECLASS,
|
||||
widget=forms.TextInput(attrs={"size": "78"}),
|
||||
help_text="Required. Defines what 'type' of entity this is. This "
|
||||
"variable holds a Python path to a module with a valid "
|
||||
"Evennia Typeclass. Defaults to "
|
||||
"settings.BASE_ACCOUNT_TYPECLASS.",
|
||||
initial={settings.BASE_ACCOUNT_TYPECLASS: settings.BASE_ACCOUNT_TYPECLASS},
|
||||
help_text="This is the Python-path to the class implementing the actual "
|
||||
"account functionality. You usually don't need to change this from"
|
||||
"the default.<BR>If your custom class is not found here, it may not be "
|
||||
"imported as part of Evennia's startup.",
|
||||
choices=adminutils.get_and_load_typeclasses(parent=AccountDB),
|
||||
)
|
||||
|
||||
db_permissions = forms.CharField(
|
||||
label="Permissions",
|
||||
initial=settings.PERMISSION_ACCOUNT_DEFAULT,
|
||||
db_lock_storage = forms.CharField(
|
||||
label="Locks",
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={"size": "78"}),
|
||||
help_text="In-game permissions. A comma-separated list of text "
|
||||
"strings checked by certain locks. They are often used for "
|
||||
"hierarchies, such as letting an Account have permission "
|
||||
"'Admin', 'Builder' etc. An Account permission can be "
|
||||
"overloaded by the permissions of a controlled Character. "
|
||||
"Normal accounts use 'Accounts' by default.",
|
||||
help_text="Locks limit access to the entity. Written on form `type:lockdef;type:lockdef..."
|
||||
"<BR>(Permissions (used with the perm() lockfunc) are Tags with the 'permission' type)",
|
||||
)
|
||||
|
||||
db_lock_storage = forms.CharField(
|
||||
|
|
@ -220,48 +240,45 @@ class AccountAdmin(BaseUserAdmin):
|
|||
This is the main creation screen for Users/accounts
|
||||
|
||||
"""
|
||||
|
||||
list_display = ("username", "email", "is_staff", "is_superuser")
|
||||
form = AccountChangeForm
|
||||
add_form = AccountCreationForm
|
||||
inlines = [AccountTagInline, AccountAttributeInline]
|
||||
readonly_fields = ["db_date_created", "serialized_string"]
|
||||
fieldsets = (
|
||||
(None, {"fields": ("username", "password", "email")}),
|
||||
(
|
||||
"Website profile",
|
||||
None,
|
||||
{
|
||||
"fields": ("first_name", "last_name"),
|
||||
"description": "<i>These are not used " "in the default system.</i>",
|
||||
"fields": (
|
||||
("username", "db_typeclass_path"),
|
||||
"password",
|
||||
"email",
|
||||
"db_date_created",
|
||||
"db_lock_storage",
|
||||
"db_cmdset_storage",
|
||||
"serialized_string",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Website dates",
|
||||
"Admin/Website properties",
|
||||
{
|
||||
"fields": ("last_login", "date_joined"),
|
||||
"description": "<i>Relevant only to the website.</i>",
|
||||
},
|
||||
),
|
||||
(
|
||||
"Website Permissions",
|
||||
{
|
||||
"fields": ("is_active", "is_staff", "is_superuser", "user_permissions", "groups"),
|
||||
"description": "<i>These are permissions/permission groups for "
|
||||
"accessing the admin site. They are unrelated to "
|
||||
"in-game access rights.</i>",
|
||||
},
|
||||
),
|
||||
(
|
||||
"Game Options",
|
||||
{
|
||||
"fields": ("db_typeclass_path", "db_cmdset_storage", "db_lock_storage"),
|
||||
"description": "<i>These are attributes that are more relevant " "to gameplay.</i>",
|
||||
"fields": (
|
||||
("first_name", "last_name"),
|
||||
"last_login",
|
||||
"date_joined",
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
"user_permissions",
|
||||
"groups",
|
||||
),
|
||||
"description": "<i>Used by the website/Django admin. "
|
||||
"Except for `superuser status`, the permissions are not used in-game.</i>",
|
||||
},
|
||||
),
|
||||
)
|
||||
# ('Game Options', {'fields': (
|
||||
# 'db_typeclass_path', 'db_cmdset_storage',
|
||||
# 'db_permissions', 'db_lock_storage'),
|
||||
# 'description': '<i>These are attributes that are '
|
||||
# 'more relevant to gameplay.</i>'}))
|
||||
|
||||
add_fieldsets = (
|
||||
(
|
||||
|
|
@ -274,6 +291,31 @@ class AccountAdmin(BaseUserAdmin):
|
|||
),
|
||||
)
|
||||
|
||||
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 it there. "
|
||||
"Note that you cannot (easily) add multiple accounts this way - better do that "
|
||||
"in code."
|
||||
)
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Overrides help texts.
|
||||
|
||||
"""
|
||||
help_texts = kwargs.get("help_texts", {})
|
||||
help_texts["serialized_string"] = self.serialized_string.help_text
|
||||
kwargs["help_texts"] = help_texts
|
||||
return super().get_form(request, obj, **kwargs)
|
||||
|
||||
@sensitive_post_parameters_m
|
||||
def user_change_password(self, request, id, form_url=""):
|
||||
user = self.get_object(request, unquote(id))
|
||||
|
|
|
|||
|
|
@ -27,17 +27,28 @@ class AttributeForm(forms.ModelForm):
|
|||
"""
|
||||
|
||||
attr_key = forms.CharField(
|
||||
label="Attribute Name", required=False, initial="Enter Attribute Name Here"
|
||||
label="Attribute Name", required=False, initial="Enter Attribute Name Here",
|
||||
help_text="The main identifier of the Attribute. For Nicks, this is the pattern-matching string."
|
||||
)
|
||||
attr_category = forms.CharField(
|
||||
label="Category", help_text="type of attribute, for sorting", required=False, max_length=128
|
||||
label="Category",
|
||||
help_text="Categorization. Unset (default) gives a category of `None`, which is "
|
||||
"is what is searched with e.g. `obj.db.attrname`. For 'nick'-type attributes, this is usually "
|
||||
"'inputline' or 'channel'.",
|
||||
required=False, max_length=128
|
||||
)
|
||||
attr_value = PickledFormField(label="Value", help_text="Value to pickle/save", required=False)
|
||||
attr_type = forms.CharField(
|
||||
attr_value = PickledFormField(
|
||||
label="Value",
|
||||
help_text="Value to pickle/save. Db-objects are serialized as a list "
|
||||
"containing `__packed_dbobj__` (they can't easily be added from here). Nicks "
|
||||
"store their pattern-replacement here.",
|
||||
required=False
|
||||
)
|
||||
attr_type = forms.ChoiceField(
|
||||
label="Type",
|
||||
help_text='Internal use. Either unset (normal Attribute) or "nick"',
|
||||
required=False,
|
||||
max_length=16,
|
||||
choices=[(None, "-"), ("nick", "nick")],
|
||||
help_text="Unset for regular Attributes, 'nick' for Nick-replacement usage.",
|
||||
required=False
|
||||
)
|
||||
attr_lockstring = forms.CharField(
|
||||
label="Locks",
|
||||
|
|
@ -172,6 +183,7 @@ class AttributeInline(admin.TabularInline):
|
|||
# Set this to the through model of your desired M2M when subclassing.
|
||||
model = None
|
||||
verbose_name = "Attribute"
|
||||
verbose_name_plural = "Attributes"
|
||||
form = AttributeForm
|
||||
formset = AttributeFormSet
|
||||
related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing
|
||||
|
|
|
|||
|
|
@ -3,14 +3,118 @@ This defines how Comm models are displayed in the web admin interface.
|
|||
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from evennia.comms.models import ChannelDB
|
||||
from evennia.comms.models import ChannelDB, Msg
|
||||
from django.conf import settings
|
||||
|
||||
from .attributes import AttributeInline
|
||||
from .tags import TagInline
|
||||
|
||||
|
||||
class MsgTagInline(TagInline):
|
||||
"""
|
||||
Inline display for Msg-tags.
|
||||
|
||||
"""
|
||||
model = Msg.db_tags.through
|
||||
related_field = "msg"
|
||||
|
||||
class MsgForm(forms.ModelForm):
|
||||
"""
|
||||
Custom Msg form.
|
||||
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
models = Msg
|
||||
fields = "__all__"
|
||||
|
||||
db_header = forms.CharField(
|
||||
label="Header",
|
||||
required=False,
|
||||
widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}),
|
||||
help_text="Optional header for the message; it could be a title or "
|
||||
"metadata depending on msg-use."
|
||||
)
|
||||
|
||||
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);...",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@admin.register(Msg)
|
||||
class MsgAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Defines display for Msg objects
|
||||
|
||||
"""
|
||||
|
||||
list_display = (
|
||||
"id",
|
||||
"db_date_created",
|
||||
"sender",
|
||||
"receiver",
|
||||
"start_of_message"
|
||||
)
|
||||
list_display_links = ("id", "db_date_created", "start_of_message")
|
||||
inlines = [MsgTagInline]
|
||||
form = MsgForm
|
||||
ordering = ["db_date_created", ]
|
||||
# readonly_fields = ['db_message', 'db_sender', 'db_receivers', 'db_channels']
|
||||
search_fields = ["id", "^db_date_created", "^db_message"]
|
||||
readonly_fields = ["db_date_created"]
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
raw_id_fields = (
|
||||
"db_date_created", "db_sender_accounts",
|
||||
"db_sender_objects", "db_sender_scripts",
|
||||
"db_receivers_accounts", "db_receivers_objects",
|
||||
"db_receivers_scripts", "db_hide_from_accounts",
|
||||
"db_hide_from_objects")
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
("db_sender_accounts", "db_sender_objects", "db_sender_scripts", "db_sender_external"),
|
||||
("db_receivers_accounts", "db_receivers_objects", "db_receivers_scripts", "db_receiver_external"),
|
||||
("db_hide_from_accounts", "db_hide_from_objects"),
|
||||
"db_header",
|
||||
"db_message"
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
def sender(self, obj):
|
||||
senders = [o for o in obj.senders if o]
|
||||
if senders:
|
||||
return senders[0]
|
||||
sender.help_text = "If multiple, only the first is shown."
|
||||
|
||||
def receiver(self, obj):
|
||||
receivers = [o for o in obj.receivers if o]
|
||||
if receivers:
|
||||
return receivers[0]
|
||||
receiver.help_text = "If multiple, only the first is shown."
|
||||
|
||||
def start_of_message(self, obj):
|
||||
crop_length = 50
|
||||
if obj.db_message:
|
||||
msg = obj.db_message
|
||||
if len(msg) > (crop_length - 5):
|
||||
msg = msg[:50] + "[...]"
|
||||
return msg
|
||||
|
||||
class ChannelAttributeInline(AttributeInline):
|
||||
"""
|
||||
Inline display of Channel Attribute - experimental
|
||||
|
|
@ -31,33 +135,26 @@ class ChannelTagInline(TagInline):
|
|||
related_field = "channeldb"
|
||||
|
||||
|
||||
class MsgAdmin(admin.ModelAdmin):
|
||||
class ChannelForm(forms.ModelForm):
|
||||
"""
|
||||
Defines display for Msg objects
|
||||
Form for accessing channels.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
model = ChannelDB
|
||||
fields = "__all__"
|
||||
|
||||
list_display = (
|
||||
"id",
|
||||
"db_date_created",
|
||||
"db_sender",
|
||||
"db_receivers",
|
||||
"db_channels",
|
||||
"db_message",
|
||||
"db_lock_storage",
|
||||
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);...",
|
||||
)
|
||||
list_display_links = ("id",)
|
||||
ordering = ["db_date_created", "db_sender", "db_receivers", "db_channels"]
|
||||
# readonly_fields = ['db_message', 'db_sender', 'db_receivers', 'db_channels']
|
||||
search_fields = ["id", "^db_date_created", "^db_message"]
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
|
||||
|
||||
# admin.site.register(Msg, MsgAdmin)
|
||||
|
||||
|
||||
@admin.register(ChannelDB)
|
||||
class ChannelAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Defines display for Channel objects
|
||||
|
|
@ -65,6 +162,7 @@ class ChannelAdmin(admin.ModelAdmin):
|
|||
"""
|
||||
|
||||
inlines = [ChannelTagInline, ChannelAttributeInline]
|
||||
form = ChannelForm
|
||||
list_display = ("id", "db_key", "no_of_subscribers", "db_lock_storage")
|
||||
list_display_links = ("id", "db_key")
|
||||
ordering = ["db_key"]
|
||||
|
|
@ -130,6 +228,3 @@ class ChannelAdmin(admin.ModelAdmin):
|
|||
from django.urls import reverse
|
||||
|
||||
return HttpResponseRedirect(reverse("admin:comms_channeldb_change", args=[obj.id]))
|
||||
|
||||
|
||||
admin.site.register(ChannelDB, ChannelAdmin)
|
||||
|
|
|
|||
|
|
@ -27,9 +27,10 @@ class HelpEntryForm(forms.ModelForm):
|
|||
label="Locks",
|
||||
initial="view:all()",
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={"size": "40"}),
|
||||
)
|
||||
|
||||
widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}),
|
||||
help_text="Set lock to view:all() unless you want it to only show to certain users."
|
||||
"<BR>Use the `edit:` limit if wanting to limit who can edit from in-game. By default it's only "
|
||||
"limited to who can use the `sethelp` command (Builders).")
|
||||
|
||||
class HelpEntryAdmin(admin.ModelAdmin):
|
||||
"Sets up the admin manaager for help entries"
|
||||
|
|
@ -48,7 +49,6 @@ class HelpEntryAdmin(admin.ModelAdmin):
|
|||
None,
|
||||
{
|
||||
"fields": (("db_key", "db_help_category"), "db_entrytext", "db_lock_storage"),
|
||||
"description": "Sets a Help entry. Set lock to <i>view:all()</I> unless you want to restrict it.",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from django.utils.translation import gettext as _
|
|||
from evennia.objects.models import ObjectDB
|
||||
from .attributes import AttributeInline
|
||||
from .tags import TagInline
|
||||
from . import utils as adminutils
|
||||
|
||||
|
||||
class ObjectAttributeInline(AttributeInline):
|
||||
|
|
@ -49,15 +50,15 @@ class ObjectCreateForm(forms.ModelForm):
|
|||
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.CharField(
|
||||
db_typeclass_path = forms.ChoiceField(
|
||||
label="Typeclass",
|
||||
initial=settings.BASE_OBJECT_TYPECLASS,
|
||||
widget=forms.TextInput(attrs={"size": "78"}),
|
||||
help_text="This defines what 'type' of entity this is. This variable holds a "
|
||||
"Python path to a module with a valid Evennia Typeclass. If you are "
|
||||
"creating a Character you should use the typeclass defined by "
|
||||
"settings.BASE_CHARACTER_TYPECLASS or one derived from that.",
|
||||
)
|
||||
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 "
|
||||
"as part of Evennia's startup.",
|
||||
choices=adminutils.get_and_load_typeclasses(parent=ObjectDB))
|
||||
|
||||
db_cmdset_storage = forms.CharField(
|
||||
label="CmdSet",
|
||||
initial="",
|
||||
|
|
@ -66,6 +67,7 @@ class ObjectCreateForm(forms.ModelForm):
|
|||
help_text="Most non-character objects don't need a cmdset"
|
||||
" and can leave this field blank.",
|
||||
)
|
||||
|
||||
raw_id_fields = ("db_destination", "db_location", "db_home")
|
||||
|
||||
|
||||
|
|
@ -75,11 +77,11 @@ class ObjectEditForm(ObjectCreateForm):
|
|||
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
model = ObjectDB
|
||||
fields = "__all__"
|
||||
|
||||
db_lock_storage = forms.CharField(
|
||||
label="Locks",
|
||||
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. "
|
||||
|
|
@ -87,6 +89,12 @@ class ObjectEditForm(ObjectCreateForm):
|
|||
"<i>type:lockfunction(args);type2:lockfunction2(args);...",
|
||||
)
|
||||
|
||||
db_typeclass_path = forms.ChoiceField(
|
||||
label="Typeclass",
|
||||
help_text="This is the Python-path to the class implementing the actual object functionality. "
|
||||
"<BR>If your custom class is not found here, it may not be imported as part of Evennia's startup.",
|
||||
choices=adminutils.get_and_load_typeclasses(parent=ObjectDB))
|
||||
|
||||
|
||||
@admin.register(ObjectDB)
|
||||
class ObjectAdmin(admin.ModelAdmin):
|
||||
|
|
@ -101,6 +109,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")
|
||||
readonly_fields = ("serialized_string", )
|
||||
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
|
|
@ -116,10 +125,10 @@ class ObjectAdmin(admin.ModelAdmin):
|
|||
{
|
||||
"fields": (
|
||||
("db_key", "db_typeclass_path"),
|
||||
("db_lock_storage",),
|
||||
("db_location", "db_home"),
|
||||
"db_destination",
|
||||
("db_location", "db_home", "db_destination"),
|
||||
"db_cmdset_storage",
|
||||
"db_lock_storage",
|
||||
"serialized_string"
|
||||
)
|
||||
},
|
||||
),
|
||||
|
|
@ -140,6 +149,19 @@ class ObjectAdmin(admin.ModelAdmin):
|
|||
),
|
||||
)
|
||||
|
||||
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 it there. "
|
||||
"Note that you cannot (easily) add multiple objects this way - better do that "
|
||||
"in code.")
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
"""
|
||||
Return fieldsets.
|
||||
|
|
@ -161,12 +183,16 @@ class ObjectAdmin(admin.ModelAdmin):
|
|||
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)
|
||||
defaults.update(kwargs)
|
||||
return super().get_form(request, obj, **defaults)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
|
|
|
|||
|
|
@ -2,12 +2,55 @@
|
|||
# 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 evennia.scripts.models import ScriptDB
|
||||
from .attributes import AttributeInline
|
||||
from .tags import TagInline
|
||||
from . import utils as adminutils
|
||||
|
||||
|
||||
class ScriptForm(forms.ModelForm):
|
||||
|
||||
db_key = forms.CharField(
|
||||
label = "Name/Key",
|
||||
help_text="Script identifier, shown in listings etc."
|
||||
)
|
||||
|
||||
db_typeclass_path = forms.ChoiceField(
|
||||
label="Typeclass",
|
||||
help_text="This is the Python-path to the class implementing the actual script functionality. "
|
||||
"<BR>If your custom class is not found here, it may not be imported as part of Evennia's startup.",
|
||||
choices=adminutils.get_and_load_typeclasses(
|
||||
parent=ScriptDB, excluded_parents=["evennia.prototypes.prototypes.DbPrototype"])
|
||||
)
|
||||
|
||||
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_interval = forms.IntegerField(
|
||||
label="Repeat Interval",
|
||||
help_text="Optional timer component.<BR>How often to call the Script's<BR>`at_repeat` hook, in seconds."
|
||||
"<BR>Set to 0 to disable."
|
||||
)
|
||||
db_repeats = forms.IntegerField(
|
||||
help_text="Only repeat this many times."
|
||||
"<BR>Set to 0 to run indefinitely."
|
||||
)
|
||||
db_start_delay = forms.BooleanField(
|
||||
help_text="Wait <B>Interval</B> seconds before first call."
|
||||
)
|
||||
db_persistent = forms.BooleanField(
|
||||
label = "Survives reboot",
|
||||
help_text="If unset, a server reboot will remove the timer."
|
||||
)
|
||||
|
||||
|
||||
class ScriptTagInline(TagInline):
|
||||
|
|
@ -17,6 +60,7 @@ class ScriptTagInline(TagInline):
|
|||
"""
|
||||
|
||||
model = ScriptDB.db_tags.through
|
||||
form = ScriptForm
|
||||
related_field = "scriptdb"
|
||||
|
||||
|
||||
|
|
@ -27,6 +71,7 @@ class ScriptAttributeInline(AttributeInline):
|
|||
"""
|
||||
|
||||
model = ScriptDB.db_attributes.through
|
||||
form = ScriptForm
|
||||
related_field = "scriptdb"
|
||||
|
||||
|
||||
|
|
@ -49,6 +94,8 @@ class ScriptAdmin(admin.ModelAdmin):
|
|||
list_display_links = ("id", "db_key")
|
||||
ordering = ["db_obj", "db_typeclass_path"]
|
||||
search_fields = ["^db_key", "db_typeclass_path"]
|
||||
readonly_fields = ["serialized_string"]
|
||||
form = ScriptForm
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
|
|
@ -60,17 +107,40 @@ class ScriptAdmin(admin.ModelAdmin):
|
|||
{
|
||||
"fields": (
|
||||
("db_key", "db_typeclass_path"),
|
||||
"db_interval",
|
||||
"db_repeats",
|
||||
"db_start_delay",
|
||||
"db_persistent",
|
||||
("db_interval", "db_repeats", "db_start_delay", "db_persistent"),
|
||||
"db_obj",
|
||||
"db_lock_storage",
|
||||
"serialized_string"
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
inlines = [ScriptTagInline, ScriptAttributeInline]
|
||||
|
||||
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 it there. "
|
||||
"Note that you cannot (easily) add multiple scripts this way - better do that "
|
||||
"in code.")
|
||||
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Overrides help texts.
|
||||
|
||||
"""
|
||||
help_texts = kwargs.get("help_texts", {})
|
||||
help_texts["serialized_string"] = self.serialized_string.help_text
|
||||
kwargs["help_texts"] = help_texts
|
||||
return super().get_form(request, obj, **kwargs)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
"""
|
||||
Model-save hook.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,47 @@ from evennia.utils.dbserialize import from_pickle, _SaverSet
|
|||
|
||||
class TagForm(forms.ModelForm):
|
||||
"""
|
||||
Form to display fields in the stand-alone Tag display.
|
||||
|
||||
"""
|
||||
|
||||
db_key = forms.CharField(
|
||||
label="Key/Name", required=True, help_text="The main key identifier"
|
||||
)
|
||||
db_category = forms.CharField(
|
||||
label="Category",
|
||||
help_text="Used for grouping tags. Unset (default) gives a category of None",
|
||||
required=False,
|
||||
)
|
||||
db_tagtype = forms.ChoiceField(
|
||||
label="Type",
|
||||
choices=[(None, "-"), ("alias", "alias"), ("permission", "permission")],
|
||||
help_text="Tags are used for different things. Unset for regular tags.",
|
||||
required=False
|
||||
)
|
||||
db_model = forms.ChoiceField(
|
||||
label="Model" ,
|
||||
required=False,
|
||||
help_text = "Each Tag can only 'attach' to one type of entity.",
|
||||
choices=([("objectdb", "objectdb"), ("accountdb", "accountdb"),
|
||||
("scriptdb", "scriptdb"), ("channeldb", "channeldb"),
|
||||
("helpentry", "helpentry"), ("msg", "msg")])
|
||||
)
|
||||
db_data = forms.CharField(
|
||||
label="Data",
|
||||
help_text="Usually unused. Intended for info about the tag itself",
|
||||
widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}),
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = ("tag_key", "tag_category", "tag_data", "tag_type")
|
||||
|
||||
|
||||
class InlineTagForm(forms.ModelForm):
|
||||
"""
|
||||
Form for displaying tags inline together with other entities.
|
||||
|
||||
This form overrides the base behavior of the ModelForm that would be used for a
|
||||
Tag-through-model. Since the through-models only have access to the foreignkeys of the Tag and
|
||||
the Object that they're attached to, we need to spoof the behavior of it being a form that would
|
||||
|
|
@ -32,13 +73,15 @@ class TagForm(forms.ModelForm):
|
|||
help_text="Used for grouping tags. Unset (default) gives a category of None",
|
||||
required=False,
|
||||
)
|
||||
tag_type = forms.CharField(
|
||||
tag_type = forms.ChoiceField(
|
||||
label="Type",
|
||||
help_text='Internal use. Either unset, "alias" or "permission"',
|
||||
required=False,
|
||||
choices=[(None, "-"), ("alias", "alias"), ("permission", "permission")],
|
||||
help_text="Tags are used for different things. Unset for regular tags.",
|
||||
required=False
|
||||
)
|
||||
tag_data = forms.CharField(
|
||||
label="Data",
|
||||
widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}),
|
||||
help_text="Usually unused. Intended for eventual info about the tag itself",
|
||||
required=False,
|
||||
)
|
||||
|
|
@ -99,6 +142,8 @@ class TagFormSet(forms.BaseInlineFormSet):
|
|||
Object, where the handler is an AliasHandler, PermissionsHandler, or TagHandler, based on the
|
||||
type of tag.
|
||||
"""
|
||||
verbose_name = "Tag"
|
||||
verbose_name_plural = "Tags"
|
||||
|
||||
def save(self, commit=True):
|
||||
def get_handler(finished_object):
|
||||
|
|
@ -137,7 +182,8 @@ class TagInline(admin.TabularInline):
|
|||
# Set this to the through model of your desired M2M when subclassing.
|
||||
model = None
|
||||
verbose_name = "Tag"
|
||||
form = TagForm
|
||||
verbose_name_plural = "Tags"
|
||||
form = InlineTagForm
|
||||
formset = TagFormSet
|
||||
related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing
|
||||
# raw_id_fields = ('tag',)
|
||||
|
|
@ -164,9 +210,25 @@ class TagInline(admin.TabularInline):
|
|||
class TagAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
A django Admin wrapper for Tags.
|
||||
|
||||
"""
|
||||
|
||||
search_fields = ("db_key", "db_category", "db_tagtype")
|
||||
list_display = ("db_key", "db_category", "db_tagtype", "db_model", "db_data")
|
||||
fields = ("db_key", "db_category", "db_tagtype", "db_model", "db_data")
|
||||
list_filter = ("db_tagtype", "db_category", "db_model")
|
||||
form = TagForm
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
("db_key", "db_category"),
|
||||
("db_tagtype", "db_model"),
|
||||
"db_data"
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
51
evennia/web/admin/utils.py
Normal file
51
evennia/web/admin/utils.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
"""
|
||||
Helper utils for admin views.
|
||||
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from evennia.utils.utils import get_all_typeclasses, inherits_from
|
||||
|
||||
|
||||
def get_and_load_typeclasses(parent=None, excluded_parents=None):
|
||||
"""
|
||||
Get all typeclasses. We we need to initialize things here
|
||||
for them to be actually available in the admin process.
|
||||
This is intended to be used with forms.ChoiceField.
|
||||
|
||||
Args:
|
||||
parent (str or class, optional): Limit selection to this class and its children
|
||||
(at any distance).
|
||||
exclude (list): Class-parents to exclude from the resulting list. All
|
||||
children of these paretns will be skipped.
|
||||
|
||||
Returns:
|
||||
list: A list of (str, str), the way ChoiceField wants it.
|
||||
|
||||
"""
|
||||
# this is necessary in order to have typeclasses imported and accessible
|
||||
# in the inheritance tree.
|
||||
import evennia
|
||||
evennia._init()
|
||||
|
||||
# this return a dict (path: class}
|
||||
tmap = get_all_typeclasses(parent=parent)
|
||||
|
||||
# filter out any excludes
|
||||
excluded_parents = excluded_parents or []
|
||||
tpaths = [path for path, tclass in tmap.items()
|
||||
if not any(inherits_from(tclass, excl) for excl in excluded_parents)]
|
||||
|
||||
# sort so we get custom paths (not in evennia repo) first
|
||||
tpaths = sorted(tpaths, key=lambda k: (1 if k.startswith("evennia.") else 0, k))
|
||||
|
||||
# the base models are not typeclasses so we filter them out
|
||||
tpaths = [path for path in tpaths if path not in
|
||||
("evennia.objects.models.ObjectDB",
|
||||
"evennia.accounts.models.AccountDB",
|
||||
"evennia.scripts.models.ScriptDB",
|
||||
"evennia.comms.models.ChannelDB",)]
|
||||
|
||||
# return on form exceptedaccepted by ChoiceField
|
||||
return [(path, path) for path in tpaths if path]
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue