astorije astorije - 1 year ago 92
Python Question

Django custom model fields: to_python() not called

I am quite new to Python and Django, and totally new on Stack Overflow, so I hope I won't break any rules here and I respect the question format.

I am facing a problem trying to implement a custom model field with Django (Python 3.3.0, Django 1.5a1), and I didn't find any similar topics, I am actually quite stuck on this one...

So there is a Player, he has got a Hand (of Card). The Hand inherits from CardContainer, which is basically a list of cards with some (hidden here) helper functions.
Here is the corresponding code:

from django.db import models

class Card:
def __init__(self, id): = id

class CardContainer:
def __init__(self, cards=None):
if cards is None:
cards = [] = cards

class Hand(CardContainer):
def __init__(self, cards=None):
super(Hand, self).__init__(cards)

class CardContainerField(models.CommaSeparatedIntegerField):
__metaclass__ = models.SubfieldBase

def __init__(self, cls, *args, **kwargs):
if not issubclass(cls, CardContainer):
raise TypeError('{} is not a subclass of CardContainer'.format(cls))

self.cls = cls
kwargs['max_length'] = 10
super(CardContainerField, self).__init__(*args, **kwargs)

def to_python(self, value):
if not value:
return self.cls()

if isinstance(value, self.cls):
return value

if isinstance(value, list):
return self.cls([i if isinstance(i, Card) else Card(i) for i in value])

# String: '1,2,3,...'
return self.cls([Card(int(i)) for i in value.split(',')])

def get_prep_value(self, value):
if value is None:
return ''

return ','.join([str( for card in])

class Player(models.Model):
hand = CardContainerField(Hand)

But when I get a player, lets say, like this:
, instead of getting a
instance (or even a
instance at all!), I am just getting a comma-separated string of integers like "1,2,3", which is fine in the database (it is the format I'd like to see IN the database)...

It seems to me that to_python doesn't get called, so the returned data is the raw value, hence the string. When I searched for this type of problems, people missed the
__metaclass__ = models.SubfieldBase
... I hoped I could have missed that too but, hey, it would have been too simple!
Did I miss something trivial, or am I wrong for the whole thing? :D

Thanks a lot!!

Answer Source

In python 3 the module-global __metaclass__ variable is no longer supported. You must use:

class CardContainerField(models.CommaSeparatedIntegerField, metaclass=models.SubfieldBase):
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download