10

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!!

2 Answers 2

12

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

class CardContainerField(models.CommaSeparatedIntegerField, metaclass=models.SubfieldBase):
   ...
Sign up to request clarification or add additional context in comments.

1 Comment

Wow, thanks, I completely didn't know that!! I think the silent result it creates is quite annoying, and I have submitted a ticket for the documentation (as Django supports Python 3, I think the doc should mention that).
0

for Django 1.10 and latest

class CardContainerField(models.CommaSeparatedIntegerField):
    def from_db_value(self,value, expression, connection, context):
      .......

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.