简体   繁体   中英

Adding dynamic fields to Django models

How do I create a dynamic field on a model?

Let's say I'm writing an application related to the stock market. I make a purchase on one day and sometime later I want to check the gain (or loss) based on today's price. I'd have a model like this:

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()

What I'd like to do is define a model something like this:

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

So that I could do this:

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)

Where p.gain is dynamically computed based on the input to get_purchase_gain. Rather than just constructing dictionaries on the fly I want to use a model, because I'd like to pass this around and generate forms, save changes, etc from the instance.

I tried creating a derived QuerySet, but that led to a circular dependency, because Purchase needed to know about the QuerySet (through a custom manager) and the QuerySet returned an iterator that needed to instantiate a PurchaseGain, which was derived from Purchase.

What options do I have?

Thanks, Craig

Why not add a gain() method to your model?

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

Then you can pretty much do what you want:

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

Creating a proxy class is what confused me. By just adding attributes to a Purchase, I was able to accomplish what I wanted.

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

Now I can do:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM