views:

59

answers:

3

Hi

Say I have the unknown number of questions. For example:

  • Is the sky blue [y/n]
  • What date were your born on [date]
  • What is pi [3.14]
  • What is a large integ [100]

Now each of these questions poses a different but very type specific answer (boolean, date, float, int). Natively django can happily deal with these in a model.

class SkyModel(models.Model):
    question = models.CharField("Is the sky blue")
    answer = models.BooleanField(default=False)

class BirthModel(models.Model):
    question = models.CharField("What date were your born on")
    answer = models.DateTimeField(default=today)

class PiModel(models.Model)
    question = models.CharField("What is pi")
    answer = models.FloatField()

But this has the obvious problem in that each question has a specific model - so if we need to add a question later I have to change the database. Yuck. So now I want to get fancy - How do a set up a model where by the answer type conversion happens automagically?

ANSWER_TYPES = (
    ('boolean', 'boolean'),
    ('date', 'date'),
    ('float', 'float'),
    ('int', 'int'),
    ('char', 'char'),
)

class Questions(models.model):
    question = models.CharField(()
    answer = models.CharField()
    answer_type = models.CharField(choices = ANSWER_TYPES)
    default = models.CharField()

So in theory this would do the following:

  • When I build up my views I look at the type of answer and ensure that I only put in that value.
  • But when I want to pull that answer back out it will return the data in the format specified by the answer_type. Example 3.14 comes back out as a float not as a str.

How can I perform this sort of automagic transformation? Or can someone suggest a better way to do this?

Thanks much!!

+1  A: 

You should be fine just storing the answers as strings. If we're talking about accepting data over the web, you're going to receive your input as a string anyway, so you're not losing precision by storing it in the database as a string.

One possible alternative would be to include one column for each possible data type, allowing them to be null.

class Questions(models.model):
    question = models.CharField(()
    answer = models.CharField()
    answer_type = models.CharField(choices = ANSWER_TYPES)
    int_answer = models.IntegerField(null=True)
    bool_answer = models.NullBooleanField(null=True)
    ... etc. 

If it were me, I'd stick with a single CharField though.

Seth
Storing it as a string is the way to go, IMHO, but having a good strategy for typecasting back to the original value is important. Having different fields for each type of answer just sounds extra-messy.
Gabriel Hurley
+3  A: 

I actually just faced this type of problem regarding extensible user settings. My solution was to store the type on the model in a CharField and use a getter to do the type conversion with a smart use of __builtin__ and getattr. This is my code (adapt for your needs):

VALUE_TYPE_CHOICES = (
    ("unicode", "Unicode String"),
    ("int", "Integer"),
    ("bool", "Boolean"),
)

class Setting(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    type = models.CharField(max_length=50, choices=VALUE_TYPE_CHOICES)
    default_value = models.CharField(max_length=127)

def get_setting(user, setting_id):
    profile_setting = #get the user's specific setting value here, not relevant
    type = getattr(__builtin__, profile_setting.setting.type)
    if type is bool:
        return type(int(profile_setting.value))
    else:
        return type(profile_setting.value)

There's one gotcha in there: bool('0') actually returns True, so I chose to typecast to int before typecasting to bool. There are other ways you can accomplish this, like using the ast module's literal_eval method instead. Overall the pattern works, though.

Gabriel Hurley
+1  A: 

I would add a custom method to your Questions class:

def get_converted_answer(self):
  if self.answer_type == 'int':
    return int(self.answer)
  if self.answer_type == 'bool':
    # ...
Bolo
I considered doing it this way, and realized I could get tricky and avoid writing all the extra ifs using the logic in my post above. Definitely the right idea, though.
Gabriel Hurley
Your trick is very nice indeed. +1
Bolo