Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django custom model fields: to_python() not called

Tags:

python

django

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):
        self.id = id


class CardContainer:
    def __init__(self, cards=None):
        if cards is None:
            cards = []
        self.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(card.id) for card in value.cards])


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

But when I get a player, lets say, like this: Player.objects.get(id=3).hand, instead of getting a Hand instance (or even a CardContainer 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!!

like image 462
astorije Avatar asked Dec 29 '12 04:12

astorije


1 Answers

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

class CardContainerField(models.CommaSeparatedIntegerField, metaclass=models.SubfieldBase):
   ...
like image 52
sneawo Avatar answered Oct 21 '22 16:10

sneawo