Attributes in django admin now also editable inline, causing them to call the AttributeHandler or NicksHandler of the appropriate object.

This commit is contained in:
Tehom 2016-11-08 04:16:10 -05:00 committed by Griatch
parent 6a851750f3
commit b80f7c3637
5 changed files with 221 additions and 64 deletions

View file

@ -14,6 +14,7 @@ class ChannelAttributeInline(AttributeInline):
""" """
model = ChannelDB.db_attributes.through model = ChannelDB.db_attributes.through
related_field = "channeldb"
class ChannelTagInline(TagInline): class ChannelTagInline(TagInline):
@ -22,6 +23,7 @@ class ChannelTagInline(TagInline):
""" """
model = ChannelDB.db_tags.through model = ChannelDB.db_tags.through
related_field = "channeldb"
class MsgAdmin(admin.ModelAdmin): class MsgAdmin(admin.ModelAdmin):

View file

@ -18,6 +18,7 @@ class ObjectAttributeInline(AttributeInline):
""" """
model = ObjectDB.db_attributes.through model = ObjectDB.db_attributes.through
related_field = "objectdb"
class ObjectTagInline(TagInline): class ObjectTagInline(TagInline):

View file

@ -182,6 +182,7 @@ class PlayerAttributeInline(AttributeInline):
""" """
model = PlayerDB.db_attributes.through model = PlayerDB.db_attributes.through
related_field = "playerdb"
class PlayerDBAdmin(BaseUserAdmin): class PlayerDBAdmin(BaseUserAdmin):

View file

@ -23,6 +23,7 @@ class ScriptAttributeInline(AttributeInline):
""" """
model = ScriptDB.db_attributes.through model = ScriptDB.db_attributes.through
related_field = "scriptdb"
class ScriptDBAdmin(admin.ModelAdmin): class ScriptDBAdmin(admin.ModelAdmin):

View file

@ -1,14 +1,14 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import ModelAdmin from django.contrib.admin import ModelAdmin
from django.core.urlresolvers import reverse
from evennia.typeclasses.models import Attribute, Tag from evennia.typeclasses.models import Attribute, Tag
from django import forms from django import forms
from evennia.utils.picklefield import PickledFormField
import traceback
class TagAdmin(admin.ModelAdmin): class TagAdmin(admin.ModelAdmin):
""" """
A django Admin wrapper for Tags. A django Admin wrapper for Tags.
""" """
search_fields = ('db_key', 'db_category', 'db_tagtype') search_fields = ('db_key', 'db_category', 'db_tagtype')
list_display = ('db_key', 'db_category', 'db_tagtype', 'db_data') list_display = ('db_key', 'db_category', 'db_tagtype', 'db_data')
@ -17,22 +17,54 @@ class TagAdmin(admin.ModelAdmin):
class TagForm(forms.ModelForm): class TagForm(forms.ModelForm):
"""
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 correspond to its tag,
or the creation of a tag. Instead of being saved, we'll call to the Object's handler, which will handle
the creation, change, or deletion of a tag for us, as well as updating the handler's cache so that all
changes are instantly updated in-game.
"""
tag_key = forms.CharField(label='Tag Name') tag_key = forms.CharField(label='Tag Name')
tag_category = forms.CharField(label="Category", required=False) tag_category = forms.CharField(label="Category", required=False)
tag_type = forms.CharField(label="Type", required=False) tag_type = forms.CharField(label="Type", required=False)
tag_data = forms.CharField(label="Data", required=False) tag_data = forms.CharField(label="Data", required=False)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""
If we have a tag, then we'll prepopulate our instance with the fields we'd expect it
to have based on the tag. tag_key, tag_category, tag_type, and tag_data all refer to
the corresponding tag fields. The initial data of the form fields will similarly be
populated.
"""
super(TagForm, self).__init__(*args, **kwargs) super(TagForm, self).__init__(*args, **kwargs)
tagkey = None
tagcategory = None
tagtype = None
tagdata = None
if hasattr(self.instance, 'tag'): if hasattr(self.instance, 'tag'):
self.fields['tag_key'].initial = self.instance.tag.db_key tagkey = self.instance.tag.db_key
self.fields['tag_category'].initial = self.instance.tag.db_category tagcategory = self.instance.tag.db_category
self.fields['tag_type'].initial = self.instance.tag.db_tagtype tagtype = self.instance.tag.db_tagtype
self.fields['tag_data'].initial = self.instance.tag.db_data tagdata = self.instance.tag.db_data
self.fields['tag_key'].initial = tagkey
self.fields['tag_category'].initial = tagcategory
self.fields['tag_type'].initial = tagtype
self.fields['tag_data'].initial = tagdata
self.instance.tag_key = tagkey
self.instance.tag_category = tagcategory
self.instance.tag_type = tagtype
self.instance.tag_data = tagdata
def save(self, commit=True): def save(self, commit=True):
"""
One thing we want to do here is the or None checks, because forms are saved with an empty
string rather than null from forms, usually, and the Handlers may handle empty strings
differently than None objects. So for consistency with how things are handled in game,
we'll try to make sure that empty form fields will be None, rather than ''.
"""
# we are spoofing a tag for the Handler that will be called # we are spoofing a tag for the Handler that will be called
#instance = super(TagForm, self).save(commit=False) # instance = super(TagForm, self).save(commit=False)
instance = self.instance instance = self.instance
instance.tag_key = self.cleaned_data['tag_key'] instance.tag_key = self.cleaned_data['tag_key']
instance.tag_category = self.cleaned_data['tag_category'] or None instance.tag_category = self.cleaned_data['tag_category'] or None
@ -42,8 +74,16 @@ class TagForm(forms.ModelForm):
class TagFormSet(forms.BaseInlineFormSet): class TagFormSet(forms.BaseInlineFormSet):
"""
The Formset handles all the inline forms that are grouped together on the change page of the
corresponding object. All the tags will appear here, and we'll save them by overriding the
formset's save method. The forms will similarly spoof their save methods to return an instance
which hasn't been saved to the database, but have the relevant fields filled out based on the
contents of the cleaned form. We'll then use that to call to the handler of the corresponding
Object, where the handler is an AliasHandler, PermissionsHandler, or TagHandler, based on the
type of tag.
"""
def save(self, commit=True): def save(self, commit=True):
print "inside TagFormSet"
def get_handler(finished_object): def get_handler(finished_object):
related = getattr(finished_object, self.related_field) related = getattr(finished_object, self.related_field)
try: try:
@ -58,32 +98,30 @@ class TagFormSet(forms.BaseInlineFormSet):
handler_name = "tags" handler_name = "tags"
return getattr(related, handler_name) return getattr(related, handler_name)
instances = super(TagFormSet, self).save(commit=False) instances = super(TagFormSet, self).save(commit=False)
# self.deleted_objects is a list created when super of save is called, we'll remove those
for obj in self.deleted_objects: for obj in self.deleted_objects:
handler = get_handler(obj) handler = get_handler(obj)
try: handler.remove(obj.tag_key, category=obj.tag_category)
tagkey = obj.tag_key
except AttributeError:
tagkey = obj.tag.db_key
handler.remove(tagkey)
for instance in instances: for instance in instances:
handler = get_handler(instance) handler = get_handler(instance)
handler.add(instance.tag_key) handler.add(instance.tag_key, category=instance.tag_category, data=instance.tag_data)
class TagInline(admin.TabularInline): class TagInline(admin.TabularInline):
""" """
A handler for inline Tags. A handler for inline Tags. This class should be subclassed in the admin of your models,
and the 'model' and 'related_field' class attributes must be set. model should be the
through model (ObjectDB_db_tag', for example), while related field should be the name
of the field on that through model which points to the model being used: 'objectdb',
'msg', 'playerdb', etc.
""" """
# Set this to the through model of your desired M2M when subclassing. # Set this to the through model of your desired M2M when subclassing.
model = None model = None
form = TagForm form = TagForm
formset = TagFormSet formset = TagFormSet
related_field = None related_field = None # Must be 'objectdb', 'playerdb', 'msg', etc. Set when subclassing
#fields = ('tag', 'key', 'category', 'data', 'tagtype')
raw_id_fields = ('tag',) raw_id_fields = ('tag',)
readonly_fields = ('tag',) readonly_fields = ('tag',)
#readonly_fields = ('key', 'category', 'data', 'tagtype')
extra = 0 extra = 0
def get_formset(self, request, obj=None, **kwargs): def get_formset(self, request, obj=None, **kwargs):
@ -94,68 +132,182 @@ class TagInline(admin.TabularInline):
people used the admin at the same time people used the admin at the same time
""" """
formset = super(TagInline, self).get_formset(request, obj, **kwargs) formset = super(TagInline, self).get_formset(request, obj, **kwargs)
class ProxyFormset(formset): class ProxyFormset(formset):
pass pass
ProxyFormset.related_field = self.related_field ProxyFormset.related_field = self.related_field
return ProxyFormset return ProxyFormset
def key(self, instance):
if not instance.id:
return "Not yet set or saved."
return '<strong><a href="%s">%s</a></strong>' % (
reverse("admin:typeclasses_tag_change",
args=[instance.tag.id]),
instance.tag.db_key)
key.allow_tags = True
def category(self, instance):
if not instance.id:
return "Not yet set or saved."
return instance.tag.db_category
def data(self, instance): class AttributeForm(forms.ModelForm):
if not instance.id: """
return "Not yet set or saved." This form overrides the base behavior of the ModelForm that would be used for a Tag-through-model.
return instance.tag.db_data 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 correspond to its tag,
def tagtype(self, instance): or the creation of a tag. Instead of being saved, we'll call to the Object's handler, which will handle
if not instance.id: the creation, change, or deletion of a tag for us, as well as updating the handler's cache so that all
return "Not yet set or saved." changes are instantly updated in-game.
return instance.tag.db_tagtype """
attr_key = forms.CharField(label='Attribute Name')
attr_category = forms.CharField(label="Category", help_text="type of attribute, for sorting", required=False)
attr_value = PickledFormField(label="Value", help_text="Value to pickle/save", required=False)
attr_type = forms.CharField(label="Type", help_text="nick for nickname, else leave blank", required=False)
attr_strvalue = forms.CharField(label="String Value",
help_text="Only enter this if value is blank and you want to save as a string",
required=False)
attr_lockstring = forms.CharField(label="Locks", required=False, widget=forms.Textarea)
def __init__(self, *args, **kwargs):
"""
If we have a tag, then we'll prepopulate our instance with the fields we'd expect it
to have based on the tag. tag_key, tag_category, tag_type, and tag_data all refer to
the corresponding tag fields. The initial data of the form fields will similarly be
populated.
"""
super(AttributeForm, self).__init__(*args, **kwargs)
attr_key = None
attr_category = None
attr_value = None
attr_strvalue = None
attr_type = None
attr_lockstring = None
if hasattr(self.instance, 'attribute'):
attr_key = self.instance.attribute.db_key
attr_category = self.instance.attribute.db_category
attr_value = self.instance.attribute.db_value
attr_strvalue = self.instance.attribute.db_strvalue
attr_type = self.instance.attribute.db_attrtype
attr_lockstring = self.instance.attribute.db_lock_storage
self.fields['attr_key'].initial = attr_key
self.fields['attr_category'].initial = attr_category
self.fields['attr_type'].initial = attr_type
self.fields['attr_value'].initial = attr_value
self.fields['attr_strvalue'].initial = attr_strvalue
self.fields['attr_lockstring'].initial = attr_lockstring
self.instance.attr_key = attr_key
self.instance.attr_category = attr_category
self.instance.attr_value = attr_value
self.instance.attr_strvalue = attr_strvalue
self.instance.attr_type = attr_type
self.instance.attr_lockstring = attr_lockstring
def save(self, commit=True):
"""
One thing we want to do here is the or None checks, because forms are saved with an empty
string rather than null from forms, usually, and the Handlers may handle empty strings
differently than None objects. So for consistency with how things are handled in game,
we'll try to make sure that empty form fields will be None, rather than ''.
"""
# we are spoofing a tag for the Handler that will be called
# instance = super(TagForm, self).save(commit=False)
instance = self.instance
instance.attr_key = self.cleaned_data['attr_key']
instance.attr_category = self.cleaned_data['attr_category'] or None
instance.attr_value = self.cleaned_data['attr_value'] or None
instance.attr_strvalue = self.cleaned_data['attr_strvalue'] or None
instance.attr_type = self.cleaned_data['attr_type'] or None
instance.attr_lockstring = self.cleaned_data['attr_lockstring']
return instance
class AttributeFormSet(forms.BaseInlineFormSet):
"""
Attribute version of TagFormSet, as above.
"""
def save(self, commit=True):
def get_handler(finished_object):
related = getattr(finished_object, self.related_field)
try:
attrtype = finished_object.attr_type
except AttributeError:
attrtype = finished_object.attribute.db_attrtype
if attrtype == "nick":
handler_name = "nicks"
else:
handler_name = "attributes"
return getattr(related, handler_name)
instances = super(AttributeFormSet, self).save(commit=False)
# self.deleted_objects is a list created when super of save is called, we'll remove those
for obj in self.deleted_objects:
handler = get_handler(obj)
handler.remove(obj.attr_key, category=obj.attr_category)
for instance in instances:
handler = get_handler(instance)
strattr = True if instance.attr_strvalue else False
value = instance.attr_value or instance.attr_strvalue
try:
handler.add(instance.attr_key, value, category=instance.attr_category, strattr=strattr,
lockstring=instance.attr_lockstring)
except (TypeError, ValueError):
# catch errors in nick templates and continue
traceback.print_exc()
continue
class AttributeInline(admin.TabularInline): class AttributeInline(admin.TabularInline):
""" """
Inline creation of player attributes.j A handler for inline Tags. This class should be subclassed in the admin of your models,
and the 'model' and 'related_field' class attributes must be set. model should be the
through model (ObjectDB_db_tag', for example), while related field should be the name
of the field on that through model which points to the model being used: 'objectdb',
'msg', 'playerdb', etc.
""" """
# Set this to the through model of your desired M2M when subclassing. # Set this to the through model of your desired M2M when subclassing.
model = None model = None
extra = 1 form = AttributeForm
#form = AttributeForm formset = AttributeFormSet
fields = ('attribute', 'key', 'value', 'strvalue') related_field = None # Must be 'objectdb', 'playerdb', 'msg', etc. Set when subclassing
raw_id_fields = ('attribute',) raw_id_fields = ('attribute',)
readonly_fields = ('key', 'value', 'strvalue') readonly_fields = ('attribute',)
extra = 0
def key(self, instance): def get_formset(self, request, obj=None, **kwargs):
if not instance.id: """
return "Not yet set or saved." get_formset has to return a class, but we need to make the class that we return
return '<strong><a href="%s">%s</a></strong>' % ( know about the related_field that we'll use. Returning the class itself rather than
reverse("admin:typeclasses_attribute_change", a proxy isn't threadsafe, since it'd be the base class and would change if multiple
args=[instance.attribute.id]), people used the admin at the same time
instance.attribute.db_key) """
formset = super(AttributeInline, self).get_formset(request, obj, **kwargs)
key.allow_tags = True class ProxyFormset(formset):
pass
def value(self, instance): ProxyFormset.related_field = self.related_field
if not instance.id: return ProxyFormset
return "Not yet set or saved."
return instance.attribute.value
def strvalue(self, instance): # class AttributeInline(admin.TabularInline):
if not instance.id: # """
return "Not yet set or saved." # Inline creation of player attributes.j
return instance.attribute.strvalue #
# """
# # Set this to the through model of your desired M2M when subclassing.
# model = None
# extra = 1
# # form = AttributeForm
# fields = ('attribute', 'key', 'value', 'strvalue')
# raw_id_fields = ('attribute',)
# readonly_fields = ('key', 'value', 'strvalue')
#
# def key(self, instance):
# if not instance.id:
# return "Not yet set or saved."
# return '<strong><a href="%s">%s</a></strong>' % (
# reverse("admin:typeclasses_attribute_change",
# args=[instance.attribute.id]),
# instance.attribute.db_key)
#
# key.allow_tags = True
#
# def value(self, instance):
# if not instance.id:
# return "Not yet set or saved."
# return instance.attribute.value
#
# def strvalue(self, instance):
# if not instance.id:
# return "Not yet set or saved."
# return instance.attribute.strvalue
class AttributeAdmin(ModelAdmin): class AttributeAdmin(ModelAdmin):
@ -163,7 +315,7 @@ class AttributeAdmin(ModelAdmin):
Defines how to display the attributes. Defines how to display the attributes.
""" """
search_fields = ('db_key', 'db_strvalue', 'db_value') search_fields = ('db_key', 'db_category')
list_display = ('db_key', 'db_strvalue', 'db_value') list_display = ('db_key', 'db_strvalue', 'db_value')
permitted_types = ('str', 'unicode', 'int', 'float', 'NoneType', 'bool') permitted_types = ('str', 'unicode', 'int', 'float', 'NoneType', 'bool')