Fix bug saving empty dicts/lists/False to admin. Resolve #1841

This commit is contained in:
Griatch 2019-06-07 22:14:44 +02:00
parent 3416db98d7
commit 432674ac7c
3 changed files with 26 additions and 28 deletions

View file

@ -73,6 +73,8 @@ Update to Python 3
- Prettifies Django 'change password' workflow - Prettifies Django 'change password' workflow
- Bugfixes - Bugfixes
- Fixes bug on login page where error messages were not being displayed - Fixes bug on login page where error messages were not being displayed
- Remove strvalue field from admin; it made no sense to have here, being an optimization field
for internal use.
### Prototypes ### Prototypes

View file

@ -1,9 +1,10 @@
import traceback
from datetime import datetime
from django.contrib import admin from django.contrib import admin
from evennia.typeclasses.models import Tag from evennia.typeclasses.models import Tag
from django import forms from django import forms
from evennia.utils.picklefield import PickledFormField from evennia.utils.picklefield import PickledFormField
from evennia.utils.dbserialize import from_pickle, _SaverSet from evennia.utils.dbserialize import from_pickle, _SaverSet
import traceback
class TagAdmin(admin.ModelAdmin): class TagAdmin(admin.ModelAdmin):
@ -170,22 +171,18 @@ class AttributeForm(forms.ModelForm):
help_text="Internal use. Either unset (normal Attribute) or \"nick\"", help_text="Internal use. Either unset (normal Attribute) or \"nick\"",
required=False, required=False,
max_length=16) max_length=16)
attr_strvalue = forms.CharField(label="String Value",
help_text="Only set when using the Attribute as a string-only store",
required=False,
widget=forms.Textarea(attrs={"rows": 1, "cols": 6}))
attr_lockstring = forms.CharField(label="Locks", attr_lockstring = forms.CharField(label="Locks",
required=False, required=False,
help_text="Lock string on the form locktype:lockdef;lockfunc:lockdef;...", help_text="Lock string on the form locktype:lockdef;lockfunc:lockdef;...",
widget=forms.Textarea(attrs={"rows": 1, "cols": 8})) widget=forms.Textarea(attrs={"rows": 1, "cols": 8}))
class Meta: class Meta:
fields = ("attr_key", "attr_value", "attr_category", "attr_strvalue", "attr_lockstring", "attr_type") fields = ("attr_key", "attr_value", "attr_category", "attr_lockstring", "attr_type")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" """
If we have an Attribute, then we'll prepopulate our instance with the fields we'd expect it If we have an Attribute, then we'll prepopulate our instance with the fields we'd expect it
to have based on the Attribute. attr_key, attr_category, attr_value, attr_strvalue, attr_type, to have based on the Attribute. attr_key, attr_category, attr_value, attr_type,
and attr_lockstring all refer to the corresponding Attribute fields. The initial data of the form fields will and attr_lockstring all refer to the corresponding Attribute fields. The initial data of the form fields will
similarly be populated. similarly be populated.
@ -194,30 +191,28 @@ class AttributeForm(forms.ModelForm):
attr_key = None attr_key = None
attr_category = None attr_category = None
attr_value = None attr_value = None
attr_strvalue = None
attr_type = None attr_type = None
attr_lockstring = None attr_lockstring = None
if hasattr(self.instance, 'attribute'): if hasattr(self.instance, 'attribute'):
attr_key = self.instance.attribute.db_key attr_key = self.instance.attribute.db_key
attr_category = self.instance.attribute.db_category attr_category = self.instance.attribute.db_category
attr_value = self.instance.attribute.db_value attr_value = self.instance.attribute.db_value
attr_strvalue = self.instance.attribute.db_strvalue
attr_type = self.instance.attribute.db_attrtype attr_type = self.instance.attribute.db_attrtype
attr_lockstring = self.instance.attribute.db_lock_storage attr_lockstring = self.instance.attribute.db_lock_storage
self.fields['attr_key'].initial = attr_key self.fields['attr_key'].initial = attr_key
self.fields['attr_category'].initial = attr_category self.fields['attr_category'].initial = attr_category
self.fields['attr_type'].initial = attr_type self.fields['attr_type'].initial = attr_type
self.fields['attr_value'].initial = attr_value self.fields['attr_value'].initial = attr_value
self.fields['attr_strvalue'].initial = attr_strvalue
self.fields['attr_lockstring'].initial = attr_lockstring self.fields['attr_lockstring'].initial = attr_lockstring
self.instance.attr_key = attr_key self.instance.attr_key = attr_key
self.instance.attr_category = attr_category self.instance.attr_category = attr_category
self.instance.attr_value = attr_value self.instance.attr_value = attr_value
# prevent set from being transformed to str
if isinstance(attr_value, set) or isinstance(attr_value, _SaverSet): # prevent from being transformed to str
if isinstance(attr_value, (set, _SaverSet)):
self.fields['attr_value'].disabled = True self.fields['attr_value'].disabled = True
self.instance.deserialized_value = from_pickle(attr_value) self.instance.deserialized_value = from_pickle(attr_value)
self.instance.attr_strvalue = attr_strvalue
self.instance.attr_type = attr_type self.instance.attr_type = attr_type
self.instance.attr_lockstring = attr_lockstring self.instance.attr_lockstring = attr_lockstring
@ -232,22 +227,22 @@ class AttributeForm(forms.ModelForm):
instance = self.instance instance = self.instance
instance.attr_key = self.cleaned_data['attr_key'] or "no_name_entered_for_attribute" instance.attr_key = self.cleaned_data['attr_key'] or "no_name_entered_for_attribute"
instance.attr_category = self.cleaned_data['attr_category'] or None instance.attr_category = self.cleaned_data['attr_category'] or None
instance.attr_value = self.cleaned_data['attr_value'] or None instance.attr_value = self.cleaned_data['attr_value']
# convert the serialized string value into an object, if necessary, for AttributeHandler # convert the serialized string value into an object, if necessary, for AttributeHandler
instance.attr_value = from_pickle(instance.attr_value) instance.attr_value = from_pickle(instance.attr_value)
instance.attr_strvalue = self.cleaned_data['attr_strvalue'] or None
instance.attr_type = self.cleaned_data['attr_type'] or None instance.attr_type = self.cleaned_data['attr_type'] or None
instance.attr_lockstring = self.cleaned_data['attr_lockstring'] instance.attr_lockstring = self.cleaned_data['attr_lockstring']
return instance return instance
def clean_attr_value(self): def clean_attr_value(self):
""" """
Prevent Sets from being cleaned due to literal_eval failing on them. Otherwise they will be turned into str. Prevent certain data-types from being cleaned due to literal_eval
failing on them. Otherwise they will be turned into str.
""" """
data = self.cleaned_data['attr_value'] data = self.cleaned_data['attr_value']
initial = self.instance.attr_value initial = self.instance.attr_value
if isinstance(initial, set) or isinstance(initial, _SaverSet): if isinstance(initial, (set, _SaverSet, datetime)):
return initial return initial
return data return data
@ -270,16 +265,19 @@ class AttributeFormSet(forms.BaseInlineFormSet):
handler_name = "attributes" handler_name = "attributes"
return getattr(related, handler_name) return getattr(related, handler_name)
instances = super().save(commit=False) instances = super().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:
# self.deleted_objects is a list created when super of save is called, we'll remove those
handler = get_handler(obj) handler = get_handler(obj)
handler.remove(obj.attr_key, category=obj.attr_category) handler.remove(obj.attr_key, category=obj.attr_category)
for instance in instances: for instance in instances:
handler = get_handler(instance) handler = get_handler(instance)
strattr = True if instance.attr_strvalue else False
value = instance.attr_value or instance.attr_strvalue value = instance.attr_value
try: try:
handler.add(instance.attr_key, value, category=instance.attr_category, strattr=strattr, handler.add(instance.attr_key, value,
category=instance.attr_category, strattr=False,
lockstring=instance.attr_lockstring) lockstring=instance.attr_lockstring)
except (TypeError, ValueError): except (TypeError, ValueError):
# catch errors in nick templates and continue # catch errors in nick templates and continue

View file

@ -135,6 +135,11 @@ class PickledWidget(Textarea):
pass pass
return super().render(name, value, attrs=attrs, renderer=renderer) return super().render(name, value, attrs=attrs, renderer=renderer)
def value_from_datadict(self, data, files, name):
dat = data.get(name)
# import evennia;evennia.set_trace()
return dat
class PickledFormField(CharField): class PickledFormField(CharField):
""" """
@ -173,12 +178,6 @@ class PickledFormField(CharField):
except (ValueError, SyntaxError): except (ValueError, SyntaxError):
pass pass
# handle datetime objects
try:
return datetime.strptime(value, "%Y-%m-%d %H:%M:%S.%f")
except ValueError:
pass
# fall through to parsing the repr() of the data # fall through to parsing the repr() of the data
try: try:
value = repr(value) value = repr(value)
@ -222,7 +221,6 @@ class PickledObjectField(models.Field):
# If the field doesn't have a default, then we punt to models.Field. # If the field doesn't have a default, then we punt to models.Field.
return super().get_default() return super().get_default()
# def to_python(self, value):
def from_db_value(self, value, *args): def from_db_value(self, value, *args):
""" """
B64decode and unpickle the object, optionally decompressing it. B64decode and unpickle the object, optionally decompressing it.