Repository: translate/pootle

Issue:
Remove SubfieldBase from MultiStringField
In django 1.9 SubFieldBase is deprecated, and removed in 1.10

Related Stackoverflow - http://stackoverflow.com/questions/35166085/how-to-deal-with-subfieldbase-has-been-deprecated-use-field-from-db-value-inst

https://docs.djangoproject.com/en/1.9/ref/models/fields/#field-api-reference

afaict we can just safely remove - it seems it just needs to have to/from db methods



Discussion:
on closer reading we might need to add a method:

```
  def from_db_value(self, value, expression, connection, context):
        ...
```

I checked this out and while apparently the change is superficial and straightforward, there is more involved: when initializing a model **manually** and setting default values for fields that didn't provide any, now these do not go through `to_python()`, which means that for `MultiStringField`s such as `source_f` and `target_f`, the values will be `None`, while previously they were `multistring([''])`.

This is problematic when [adding new units to a store](https://github.com/translate/pootle/blob/05fb3865233b04b16afd0b2ac1cc944a664f3e49/pootle/apps/pootle_store/models.py#L1404), as many pieces of code following it rely on `<field>.strings` being available, which will only be the case if the field is initialized to a proper `multistring`.

As a side note, realize `from_db_value()` is invoked, as the name says, only when initializing the model via the DB data (this also applies to using `values()`, `values_list()` or other aggregate functions).


Resolving patch:
diff --git a/pootle/apps/pootle_store/fields.py b/pootle/apps/pootle_store/fields.py
--- a/pootle/apps/pootle_store/fields.py
+++ b/pootle/apps/pootle_store/fields.py
@@ -48,10 +48,30 @@ def to_python(value):
         return multistring(value, encoding="UTF-8")
 
 
+class CastOnAssignDescriptor(object):
+    """
+    A property descriptor which ensures that `field.to_python()` is called on
+    _every_ assignment to the field.  This used to be provided by the
+    `django.db.models.subclassing.Creator` class, which in turn was used by the
+    deprecated-in-Django-1.10 `SubfieldBase` class, hence the reimplementation
+    here.
+    """
+
+    def __init__(self, field):
+        self.field = field
+
+    def __get__(self, obj, type=None):
+        if obj is None:
+            return self
+        return obj.__dict__[self.field.name]
+
+    def __set__(self, obj, value):
+        obj.__dict__[self.field.name] = self.field.to_python(value)
+
+
 class MultiStringField(models.Field):
     description = \
         "a field imitating translate.misc.multistring used for plurals"
-    __metaclass__ = models.SubfieldBase
 
     def __init__(self, *args, **kwargs):
         super(MultiStringField, self).__init__(*args, **kwargs)
@@ -62,6 +82,9 @@ def get_internal_type(self):
     def to_python(self, value):
         return to_python(value)
 
+    def from_db_value(self, value, expression, connection, context):
+        return to_python(value)
+
     def get_prep_value(self, value):
         return to_db(value)
 
@@ -72,6 +95,10 @@ def get_prep_lookup(self, lookup_type, value):
         return super(MultiStringField, self).get_prep_lookup(lookup_type,
                                                              value)
 
+    def contribute_to_class(self, cls, name):
+        super(MultiStringField, self).contribute_to_class(cls, name)
+        setattr(cls, name, CastOnAssignDescriptor(self))
+
 
 # # # # # # # # # File # # # # # # # # # # # # # # # #
 


Test patch: