向Django模型添加动态字段

3

我该如何在一个模型上创建动态字段?

假设我正在开发与股票市场有关的应用程序。我在某一天购买了股票,过了一段时间后我想根据今天的价格检查收益(或亏损)。我会有一个像这样的模型:

class Purchase(models.Model):
  ticker = models.CharField(max_length=5)
  date = models.DateField()
  price = models.DecimalField(max_digits=20, decimal_places=3)
  quantity = models.IntegerField()

我希望能定义一个类似于这样的模型:

class PurchaseGain(Purchase):
  gain = models.DecimalField(max_digits=20, decimal_places=3)
  class Meta:
    proxy = True

为了能够做到这一点:
todays_price = get_price_from_webservice(ticker)
for p in PurchaseGain.objects.get_purchase_gain(todays_price):
  print '%s bought on %s for a gain of %s' % (p.ticker, p.date, p.gain)

在获取购买收益时,p.gain是根据输入动态计算的。我希望使用模型来构建字典,因为我想将其传递并生成表单,保存更改等。

我尝试创建一个派生的QuerySet,但这导致了循环依赖,因为Purchase需要知道QuerySet(通过自定义管理器),而QuerySet返回一个迭代器,需要实例化一个从Purchase派生的PurchaseGain。

我有哪些选项?

谢谢, Craig

2个回答

2

为什么不在你的模型中添加一个gain()方法呢?

class Purchase(models.Model):
    ticker = models.CharField(max_length=5)
    date = models.DateField()
    price = models.DecimalField(max_digits=20, decimal_places=3)
    quantity = models.IntegerField()

    def gain(self, todays_price=None):
        if not todays_price:
            todays_price = get_price_from_webservice(self.ticker)
        result_gain = todays_price - self.price
        return result_gain

那么你几乎可以做你想做的任何事情:

for p in Purchase.objects.all():
    print '%s bought on %s for a gain of %s' % (p.ticker, p.date, p.gain())

2
你甚至可以使用@property装饰器,这样你只需将其称为p.gain - Daniel Roseman
谢谢。那个方法可以行得通,但不具有可扩展性。我可能会为单个股票拥有数百或数千个购买记录。我正在寻找一种预先计算并传递到查询中的方法。虽然也许我可以考虑缓存远程调用。我需要再想一想。 - Craig Howard
我不应该在醒来后立即回复。我之所以没有这样做的原因是现在HTTP调用已经硬编码到我的模型中,这使得单元测试几乎不可能。我想要一些东西,可以依赖注入一个真正的HTTP客户端或一个模拟客户端进行测试。这就是我喜欢上面提出的模型的原因。 - Craig Howard
好的,我已经在增益方法中添加了一个可选的“new_price”参数,这将允许测试。我同意你上面的评论,如果你关心性能,你应该考虑以某种方式进行缓存。你仍然可以将缓存检索放在gain()方法中。 - monkut

2

创建代理类让我感到困惑。但是,只需向购买(Purchase)中添加属性,我就能够实现自己想要的功能。

class PurchaseQuerySet(QuerySet):
  def __init__(self, *args, **kwargs):
    super(PurchaseQuerySet, self).__init__(*args, **kwargs)
    self.todays_price = None

  def get_with_todays_price(self, todays_price):
    self.todays_price = todays_price
    cloned = self.all()
    cloned.todays_price = todays_price
    return cloned

  def iterator(self):
    for p in super(PurchaseQuerySet, self).iterator():
      p.todays_price = self.todays_price
      yield p

class PurchaseManager(models.Manager):
  def get_query_set(self):
    return PurchaseQuerySet(self.model)

  def __getattr__(self, name)
    return getattr(self.get_query_set(), name)

class Purchase(models.Model):
  ticker = models.CharField(max_length=5)
  date = models.DateField()
  price = models.DecimalField(max_digits=20, decimal_places=3)
  quantity = models.IntegerField()

  objects = PurchaseManager()

  @property
  def gain(self):
    return self.todays_price - self.price

现在我可以做到:

for p in Purchase.objects.filter(ticker=ticker).get_with_todays_price(100):
  print p
  print p.gain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接