1

The code I have below was derived from this Question

class CryptoData(models.Model):
    currency = models.CharField(max_length=20, choices=currency_choices, default='BTC')
    amount = models.IntegerField()
    price_with_amount = 1


    def calculate_price(self):
        if self.currency == "BTC":
            currency_price = get_crypto_price("bitcoin")
        elif self.currency == "ETH":
            currency_price = get_crypto_price("ethereum")
        elif self.currency == "UNI":
            currency_price = get_crypto_price("uniswap")
        elif self.currency == "ADA":
            currency_price = get_crypto_price("cardano")
        elif self.currency == "BAT":
            currency_price = get_crypto_price("basic attention token")

        price_with_amount = currency_price * self.amount

        return price_with_amount

    def save(self,*args,**kwargs):
        self.price_with_amount = self.calculate_price()
        super().save(*args, **kwargs)


    class Meta:
        verbose_name_plural = "Crypto Data"


    def __str__(self):
        return f'{self.currency}-{self.amount}-{self.price_with_amount}'

Basically, I want to multiply the user input, amount, by the price I obtain using my get_crypto_price function (I have confirmed that the get_crypto_price function works). After saving self.price_with_amount, I want to return it in my str method then pass it to my views.py to be used in my HTML. When I give price_with_amount a value of 1 for example, as I did in my code, it gets passed and works fine in my HTML. What I'm trying to do is change the value of price_with_amount to the obtained values in the method calculate_price. How can this be done while keeping the methods I currently have?

Thanks :)

Adamfk1
  • 23
  • 1
  • 4

1 Answers1

3

If you really want to save it to the database, you could make the field a models.FloatField with the editable=False argument, so that it doesn't appear in the form for the user to edit. Then the rest of your code should work as is.

But...

Since price_with_amount is a dynamic value that's based on the current price, via the get_crypto_price function, it doesn't seem like a good idea to save it.

Instead, you could calculate the value on-the-fly using a @property. A property is an instance method that you can use as if it were a regular attribute.

Your code could be rewritten like this:

class CryptoData(models.Model):
    currency = models.CharField(...)
    amount = models.IntegerField()

    ...

    @property
    def price_with_amount(self):
        return self.calculate_price()

    def __str__(self):
        return f'{self.currency}-{self.amount}-{self.price_with_amount}'

With this implementation, you can always access the price_with_amount property on a CryptoData instance, and as long as it has the currency and amount values, it will show the value calculated on demand.

>>> crypto = CryptoData(currency="ETH", amount=10)
>>> crypto.price_with_amount
22718.20
>>> crypto.save()

# It still behaves as expected after fetching it from the db
>>> CryptoData.objects.get(id=crypto.id).price_with_amount
22718.20

# You can use the property just like you would a regular attribute
>>> if crypto.price_with_amount > 1000:
        print("foobar")
foobar
damon
  • 14,485
  • 14
  • 56
  • 75
  • It works now using the property function. Thanks :) I have one question though: Using the property function, I wont need to use a save method correct? – Adamfk1 Jun 30 '21 at 23:54
  • That's correct. Now the value is calculated on-demand and not saved to the db, so it doesn't need to be referenced in the `save()` method anymore. – damon Jul 01 '21 at 17:11