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

View file

@ -1,9 +1,10 @@
import traceback
from datetime import datetime
from django.contrib import admin
from evennia.typeclasses.models import Tag
from django import forms
from evennia.utils.picklefield import PickledFormField
from evennia.utils.dbserialize import from_pickle, _SaverSet
import traceback
class TagAdmin(admin.ModelAdmin):
@ -170,22 +171,18 @@ class AttributeForm(forms.ModelForm):
help_text="Internal use. Either unset (normal Attribute) or \"nick\"",
required=False,
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",
required=False,
help_text="Lock string on the form locktype:lockdef;lockfunc:lockdef;...",
widget=forms.Textarea(attrs={"rows": 1, "cols": 8}))
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):
"""
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
similarly be populated.
@ -194,30 +191,28 @@ class AttributeForm(forms.ModelForm):
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
# 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.instance.deserialized_value = from_pickle(attr_value)
self.instance.attr_strvalue = attr_strvalue
self.instance.attr_type = attr_type
self.instance.attr_lockstring = attr_lockstring
@ -232,22 +227,22 @@ class AttributeForm(forms.ModelForm):
instance = self.instance
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_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
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_lockstring = self.cleaned_data['attr_lockstring']
return instance
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']
initial = self.instance.attr_value
if isinstance(initial, set) or isinstance(initial, _SaverSet):
if isinstance(initial, (set, _SaverSet, datetime)):
return initial
return data
@ -270,16 +265,19 @@ class AttributeFormSet(forms.BaseInlineFormSet):
handler_name = "attributes"
return getattr(related, handler_name)
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:
# self.deleted_objects is a list created when super of save is called, we'll remove those
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
value = instance.attr_value
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)
except (TypeError, ValueError):
# catch errors in nick templates and continue

View file

@ -135,6 +135,11 @@ class PickledWidget(Textarea):
pass
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):
"""
@ -173,12 +178,6 @@ class PickledFormField(CharField):
except (ValueError, SyntaxError):
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
try:
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.
return super().get_default()
# def to_python(self, value):
def from_db_value(self, value, *args):
"""
B64decode and unpickle the object, optionally decompressing it.