Repository: evennia/evennia

Issue:
Attributes in django admin aren't converting from unicode
#### Brief summary of issue / Description of requested feature:
Not sure why, but the Attribute formset I put in django admin to handle adding attributes to objects, players, etc doesn't seem to be converting properly, at least for objects that have a large number of Attributes. It casts everything to unicode and leaves them that way.

Edit: After investigating with the staff member who caused the error, it seems like it's caused by how the formset is populated upon raising a validation error. Nicks are currently throwing a validation error due to 'inputline' being how they were saved and it only permitting 4 characters in that field rather than 9.

Edit: Figured this out. Brief explanation of why this happened - it was the custom widget used for picklefields that caused the error on validation. The widget called repr() on the value to then test whether it could be run through literal_eval: if it couldn't, then it was assumed to be non-safe and made readonly. The problem is, the widget would be called again for the same value if a ValidationError was raised, making the string into the format of `repr(repr(string))` and so on each time an error was raised. Running literal_eval on them then would just turn them back into strings rather than the original objects they represented.

#### Steps to reproduce the issue / Reasons for adding feature:

1. Raise a validation error in the page.

#### Error output / Expected result of feature
Attributes are converted to unicode


#### Extra information, such as Evennia revision/repo/branch, operating system and ideas for how to solve / implement:
Not sure why this has started happening. I'll definitely try to fix this when I can, I just wanted to note it here for others since it's a pretty nasty bug - it can essentially make all attributes on an object unusuable. Just be sure to go back rather than save if you get a validation error on the page.



Discussion:

Resolving patch:
diff --git a/evennia/typeclasses/admin.py b/evennia/typeclasses/admin.py
--- a/evennia/typeclasses/admin.py
+++ b/evennia/typeclasses/admin.py
@@ -2,7 +2,7 @@
 from evennia.typeclasses.models import Tag
 from django import forms
 from evennia.utils.picklefield import PickledFormField
-from evennia.utils.dbserialize import from_pickle
+from evennia.utils.dbserialize import from_pickle, _SaverSet
 import traceback
 
 
@@ -164,12 +164,12 @@ class AttributeForm(forms.ModelForm):
     attr_category = forms.CharField(label="Category",
                                     help_text="type of attribute, for sorting",
                                     required=False,
-                                    max_length=4)
+                                    max_length=128)
     attr_value = PickledFormField(label="Value", help_text="Value to pickle/save", required=False)
     attr_type = forms.CharField(label="Type",
                                 help_text="Internal use. Either unset (normal Attribute) or \"nick\"",
                                 required=False,
-                                max_length=4)
+                                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,
@@ -213,6 +213,9 @@ def __init__(self, *args, **kwargs):
         self.instance.attr_key = attr_key
         self.instance.attr_category = attr_category
         self.instance.attr_value = attr_value
+        # prevent set from being transformed to unicode
+        if isinstance(attr_value, set) or isinstance(attr_value, _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
@@ -237,6 +240,17 @@ def save(self, commit=True):
         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
+        unicode.
+        """
+        data = self.cleaned_data['attr_value']
+        initial = self.instance.attr_value
+        if isinstance(initial, set) or isinstance(initial, _SaverSet):
+            return initial
+        return data
+
 
 class AttributeFormSet(forms.BaseInlineFormSet):
     """
diff --git a/evennia/utils/picklefield.py b/evennia/utils/picklefield.py
--- a/evennia/utils/picklefield.py
+++ b/evennia/utils/picklefield.py
@@ -120,9 +120,11 @@ def dbsafe_decode(value, compress_object=False):
 
 class PickledWidget(Textarea):
     def render(self, name, value, attrs=None):
+        """Display of the PickledField in django admin"""
         value = repr(value)
         try:
-            literal_eval(value)
+            # necessary to convert it back after repr(), otherwise validation errors will mutate it
+            value = literal_eval(value)
         except ValueError:
             return value
 


Test patch: