views:

441

answers:

3

I'm trying to make some types in Django that map to standard Django types. The custom model field documentation goes into complicated cases; I just want to store a basic Django type from a class with a bunch of handy methods.

For example, if I were storing playing cards, I want something like:

class Card(object):
    """ A playing card.  """
    def as_number(self):
        """ returns a number from 1 (Ace of Clubs) and 52 (King of Spades)."""
        return self.number + self.suit_rank() * 13
    def __unicode(self): ...
    def is_highest(self, other_cards, trump=None):...
    def __init__(self, number, suit):  ...
     ...

I want my models to have something like:

class my_game(models.Model):
    ante = models.IntegerField()
    bonus_card = Card()   # Really stored as an models.IntegerField()
    ....

I'm expecting the answer will look like inheriting from the correct type, adding some specially named get/store fields for card, and renaming init(). Does anyone have sample code or better documentation?

+2  A: 

Why can't you do something like the following?

class Card(models.Model):
    """ A playing card.  """
    self.suit = models.PositiveIntegerField()
    self.rank = models.PositiveIntegerField( choices=SUIT_CHOICES )
    def as_number(self):
        """ returns a number from 1 (Ace of Clubs) and 52 (King of Spades)."""
        return self.number + self.suit * 13
    def __unicode__(self):
        return ...
    def is_highest(self, other_cards, trump=None):...

Certainly, this is quite simple, and fits comfortably with what Django does naturally.

S.Lott
This only works in the trivial case, creates a new table to manage, and burns about 100x more storage and computation time. Thanks for the attempt.
Charles Merriam
Don't know what "trivial case" means, since this always works. The integer uses 52*4 bytes, this uses 100*52*4 = 21K bytes, still nothing. And you don't manage the tables, Django does. Don't see the actual problem yet.
S.Lott
A: 

Don't be afraid to adapt the model classes in Django to your own needs. There's nothing magical about them. And I guess this is the Right Place for this code: In the model.

rebra
+3  A: 

I'd do this with a subclass of Django's PositiveIntegerField:

from django.db import models

class Card(object):
    """The ``Card`` class you described."""
    ...

class CardField(models.PositiveIntegerField):
    __metaclass__ = models.SubfieldBase

    def get_db_prep_value(self, value):
        """Return the ``int`` equivalent of ``value``."""
        if value is None: return None
        try:
            int_value = value.as_number()
        except AttributeError:
            int_value = int(value)
        return int_value

    def to_python(self, value):
        """Return the ``Card`` equivalent of ``value``."""
        if value is None or isinstance(value, Card):
            return value
        return Card(int(value))

The get_db_prep_value method is responsible for converting value into something suitable for interacting with the database, in this case either an int or None.

The to_python method does the reverse, converting value into a Card. Just like before, you'll need to handle the possibility of None as a value. Using the SubfieldBase ensures that to_python is called every time a value is assigned to the field.

jpwatts