Start cleaning up the admin

This commit is contained in:
Griatch 2021-05-19 00:39:51 +02:00
parent efe3a28343
commit d11ab6d0ee
10 changed files with 476 additions and 111 deletions

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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)

View file

@ -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.",
},
),
)

View file

@ -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):

View file

@ -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.

View file

@ -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"
)
},
),
)

View 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]