开发者

How do I store values of arbitrary type in a single Django model?

开发者 https://www.devze.com 2022-12-27 09:16 出处:网络
Say I have the unknown number of questions.For e开发者_StackOverflowxample: Is the sky blue [y/n] What date were your born on [date]

Say I have the unknown number of questions. For e开发者_StackOverflowxample:

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


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.


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.


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':
    # ...
0

精彩评论

暂无评论...
验证码 换一张
取 消