2

I have a Django model that represents a financial model. This model has a one-to-many relationship with another set of models that also contain financial data. In my database I store the values in USD ($), however when I come to use the models I want to be able to convert between EURs and GBP.

Therefore, I have added a method in my models called convert_to_ccy(rate) and if I call this I want it to convert the related models as well (via this one-to-many relationship). Here is a very simple example to illustrate what I am trying to do.

class main_model_example(models.Model):

    INCLUDE_IN_CCY_CONVERSION = ['value_field_1', 'value_field_2' etc...]

    # FIELD DEFINITIONS
    date = models.DateField()
    value_field_1 = models.DecimalField(max_digits=20, decimal_places=2, default=0.00)
    value_field_2 = models.DecimalField(max_digits=20, decimal_places=2, default=0.00)
    ...etc...

    # METHODS
    def convert_to_ccy(self, rate):
    """Converts $ fields"""
        for field in self._meta.get_all_field_names():
            if field in self.INCLUDE_IN_CCY_CONVERSION:
                value = getattr(self, field)
                    if value != None:
                    setattr(self, field, float(value) / rate)

        # NOW CONVERT THE RELATED MODELS
        for position in self.the_related_model_set.all():
            position.convert_to_ccy_rate(rate)


# THE RELATED MODEL
class the_related_model(models.Model):

    INCLUDE_CCY_CONVERSION = ['yet_another_finacial_field']

    main_model_example = models.ForeignKey(main_model_example, on_delete=models.CASCADE)
    yet_another_financial_field = models.DecimalField(max_digits=20, decimal_places=2, default=0.00)
    ...and so on....

    def convert_to_ccy(self, rate):
    """Converts $ fields"""
        for field in self._meta.get_all_field_names():
            if field in self.INCLUDE_IN_CCY_CONVERSION:
                value = getattr(self, field)
                    if value != None:
                    setattr(self, field, float(value) / rate)

This works, and converts the correct fields. In my code (views.py etc) I pass around the main model which retains the new values I have set (ie in EURs).

However, when I come to get information off the related models again (via the main model using model.the_related_model_set.all() the values are back to being in USD. I assume this is because the xxx_set.all() method retrieves the models from the database again (which has NOT been updated). What I would like is once I have updated the related models, for them to remain in memory so when I come to use them again the retain the updated values I have set. I have read the documentation and browsed questions but cannot seem to find anything similar.

So I have two questions:

  1. Is what I want to achieve possible? I.e updating the related (one-to-many) models in memory, passing around the main model (doing more calculations) and then getting back the updated values when I reference the related models again later.

Or do I need to get all the related models off the main model at the off-set and assign them to a variable which i have to pass around with the main model?

  1. Is the fact that I am doing it this way considered bad? I.e. having the main models method call the related models methods? I am not overly comfortable with my implementation which often suggests there is a better way.

Any advice on better ways to achieve this would be greatly appreciated.

Thanks.

Moe Far
  • 2,742
  • 2
  • 23
  • 41
rioubenson
  • 401
  • 1
  • 4
  • 16
  • Not sure about the error, but wouldn't you want to maintain values in their respective currency, and only do the conversion when you want to show the user? If you have an item in EUR today and convert it to USD, then tomorrow when the value of the dollar changes, your database information is incorrect. – flakes Feb 12 '16 at 16:17
  • Yes, good question. Sorry if I wasn't clear. So I do only convert when I show to the user - for example when the user clicks 'view report' I pull the values out of a database (in USD which have all been pre-calculated), get the current exchange rate, convert to EURs and then show the user (via a couple of additional calculations I tag on). The database is only populated in USD. Now, I could write a custom filter to do the conversion on the template side, but due to the number of fields I show in the report (dozens and dozens) I feel it is better to do so in the view side. – rioubenson Feb 12 '16 at 16:23
  • 1
    It is worth mentioning that when I do the currency conversion, I DO NOT want this to propagate to the database. – rioubenson Feb 12 '16 at 16:24

1 Answers1

3

I'm sorry, but I'm having a difficult time following your example; though I think this is what you want.

A django Queriest has an .update() method that can pass new attributes to update into, and the values will persist.

my_django_class.objects.all().update(my_class_attr=new_value)

To get your query set, you can use a related name in your query. Django produces them by default, but I find it is better to explicitly define them. this This stack question addresses them well.

that way, your method would look like this (if I'm reading it right):

def _update_currancies(self):
    Some_other_model(_the_related_name__field = main_model).update(attr = new_value)

I find it is better not to update tables when a request is fired. Typically I would just add two other fields and every time the input value changes (USD in this case), use methods to just update the additional fields (can be done by overwriting the save method).

for your case, it would look like this:

class main_model_example(models.Model):
   us_number = models.DecimalField(max_digits=20, decimal_places=2, default=0.00)
   eur_number = models.DecimalField(max_digits=20, decimal_places=2, default=0.00)
   ccy_number = models.DecimalField(max_digits=20, decimal_places=2, default=0.00)

   def _update_currancies(self):
       #update and set new currency values
       #e.g. self.eur_number = self.us_number * .96

   def save(self, *args, **kwargs):
       self._update_currancies()
       super(main_model_example, self).save(*args, **kwargs)
Community
  • 1
  • 1
Nick Brady
  • 6,084
  • 1
  • 46
  • 71
  • Hi Nick. Thanks for the answer. If you are finding it hard to follow my example then then just strengthens the fact that I am probably doing it wrong/over complicating matters. I understand your reply but the problem is that I do not want to persist the changes when I convert from USD to another currency. Converting to another currency is just for the end report that I present to the user. So all I want to do is update the values on the main model instance AND related models (via the one-to-many relationship) as I pass it from view to template, but not touch the underlying data. – rioubenson Feb 15 '16 at 09:21
  • 1
    I see. In that case. I would use django-Tastypie. It is a library commonly used to write APIs for django projects and more. You can overwrite the dehydrate method of the resource for your models to generate the new currencies on the fly, not ever touching your actual database. – Nick Brady Feb 15 '16 at 20:32