Opened 11 years ago

Closed 11 years ago

Last modified 11 years ago

#7974 closed (wontfix)

cannot replace custom fields without using database format

Reported by: mspang Owned by: nobody
Component: Database layer (models, ORM) Version: 1.0-alpha
Severity: Keywords:
Cc: Jacob Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no


If you use to_python() to build a Python class for a custom field (as explained in the tutorial), you lose the ability to directly assign to that field. Example:


class Int(object):
  def __init__(self, value):
    self.int_value = value

class IntField(models.Field):
  __metaclass__ = models.SubfieldBase

  def get_default(self):
    return 0

  def to_python(self, value):
    return Int(int(value))

  def get_db_prep_save(self, value):
    return value.int_value

  def db_type():
    return 'int'

class IntModel(models.Model):
  int_field = IntField()


>>> a = models.IntModel()
>>> a.int_field
<testproj.testapp.models.Int object at 0xf696cf8c>
>>> a.int_field.int_value
>>> a.int_field.int_value = 10
>>> a.int_field.int_value
>>> a.int_field = models.Int(30)
Traceback (most recent call last):
  File "<console>", line 1, in ?
  File "/opt/django/db/models/fields/", line 34, in __set__
    obj.__dict__[] = self.field.to_python(value)
  File "/home/mspang/testproj/testapp/", line 97, in to_python
    return Int(int(value))
TypeError: int() argument must be a string or a number

We can never assign to int_field directly using its python type. This is especially crippling if the object itself is immutable, since in that case you have to serialize and deserialize the field once per change. To solve this, to_python() must not be called during assignment, only during database loads. Introducing a new method ('get_db_prep_load') for this purpose seems appropriate.

Change History (2)

comment:1 Changed 11 years ago by Jacob

Resolution: wontfix
Status: newclosed

This is by design: SubfieldBase is a shortcut for using some transparent user-defined type. If you want to directly assign that type, you need to either write a laxer to_python[*], or else implement the descriptor on the field yourself.

[*] like this:

def to_python(self, value):
    return value if isinstance(value, Int) else Int(int(value))

comment:2 Changed 11 years ago by mspang

Cc: Jacob added

That works for my contrived example, but consider:

How would you write a field that stores data in the database in base64, and expects to be assigned non-base64 strings? You can't do it, because to_python() doesn't know whether it's coming from database or the user.

Overloading to_python() to do both "load from the database" and "set by user" is fairly limiting. Unfortunately I didn't have time to work up a patch that would cleanly separate them so Django 1.0 will also have this problem. You can't work around it by writing your own descriptor, either.

Note: See TracTickets for help on using tickets.
Back to Top