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:
parent
6a851750f3
commit
b80f7c3637
5 changed files with 221 additions and 64 deletions
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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,20 +17,52 @@ 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
|
||||||
|
|
@ -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):
|
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_category
|
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.
|
||||||
|
"""
|
||||||
|
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 data(self, instance):
|
def __init__(self, *args, **kwargs):
|
||||||
if not instance.id:
|
"""
|
||||||
return "Not yet set or saved."
|
If we have a tag, then we'll prepopulate our instance with the fields we'd expect it
|
||||||
return instance.tag.db_data
|
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 tagtype(self, instance):
|
def save(self, commit=True):
|
||||||
if not instance.id:
|
"""
|
||||||
return "Not yet set or saved."
|
One thing we want to do here is the or None checks, because forms are saved with an empty
|
||||||
return instance.tag.db_tagtype
|
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')
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue