5
\$\begingroup\$

I'm coming from Java so I am used to performing calculations I require in separate functions. I would like to know if the below code makes sense in my Django model especially the loop and calling functions from within functions.

The aim of this is to create a backend for multiple Litecoin miners (L3's). Every miner will contribute to the same pool but be plugged in at different times. Therefore each will get its own time stamp. Each month I will pull the total pool amount (pulled from api) and use the total amount of hours contributed to the pool to calculate the litecoins-to-hour ratio. This ratio I can then multiply to each miners' hours worked for the month to determine that particular miners Litecoin earned.

class L3(models.Model):
    user = models.ForeignKey(User, related_name="L3")
    MachineID = models.CharField(max_length=256)
    TimeOnline = models.DateTimeField()
    Commission = models.IntegerField(default='10')
    TimeOffline = models.IntegerField(default='0')

    def indivMachineHoursWorked(self):
        this_month = datetime.now().month
        if self.TimeOnline == this_month:
            x = datetime.now() - self.TimeOnline
        else:
            x = datetime.now() - datetime.today().replace(day=1) #need to update hour to 0 too i.e. start of month
        return x

    def totalMachineHoursWorked(self):
        L3_total = L3.objects.all()
        hrs=0
        for l3 in L3_total:
            hrs =+ l3.indivMachineHoursWorked()
        return hrs

    def btcToHour(self):
        main_api = 'https://api.nicehash.com/api?method=balance&id=123456&key=01234567-abcd-dcba-abcd-0123456789abc'
        url = main_api
        json_data = requests.get(url).json()
        print(json_data)
        balance1 = json_data['result']['balance_confirmed']
        print(balance1)
        btc2hour = balance1/self.totalMachineHoursWorked()
        return btc2hour

    def indivMachineTurnover(self):
        return self.indivMachineHoursWorked()*self.btcToHour()

    def __str__(self):
        return self.user.get_full_name()
\$\endgroup\$
  • \$\begingroup\$ Don't change your code after receiving answers. It's considered answer invalidation. \$\endgroup\$ – Mast Nov 12 '17 at 14:58
3
\$\begingroup\$

To expand on @200_success answer, you may want to have a look into Django's queryset annotation / aggregation and F objects.

So, basically, if you can work out the ORM equivalent to your indivMachineHoursWorked (which probably involve F('TimeOnLine') or F('TimeOnLine__month')), your total hours are

L3.objects.annotate(hours_worked=<whatever>).aggregate(Sum('hours_worked'))

Besides, this whole logic seems more like business logic that should lie in a view rather than in the model. In such place you would be able to:

  • Build a single queryset to annotate hours worked this month
  • Cache the result of the aggregation to not recompute it for each object
  • Annotate the queryset again to get the turnover

The view could start with something like

hours_per_machine = L3.objects.annotate(hours_worked=<whatever>)
btc_to_hour = compute_hour_ratio(hours_per_machine.aggregate(Sum('hours_worked'))['hours_worked__sum'])
machine_turnover = hours_per_machine.annotate(turnover=F('hours_worked') * btc_to_hours)
for machine in machine_turnover:
    # do something with machine.turnover

Where

def compute_hour_ratio(hours_worked):
    response = requests.get(
            'https://api.nicehash.com/api',
            params={
                'method': 'balance',
                'id': '123456',
                'key': '01234567-abcd-dcba-abcd-0123456789abc',
            })
    response.raise_for_status()
    balance = response.json()['result']['balance_confirmed']
    return balance / hours_worked

To answer your comment about result caching:

Django comes with an impressively configurable cache system. So you may want to take advantage of it. First off, choose your cache backend and configure it properly (let's say, store the value in the database). Then, in your view, you could query the cache manually:

from django.core.cache import cache


def my_view(request):
    hours_per_machine = L3.objects.annotate(hours_worked=<whatever>)
    btc_to_hour = cache.get('btc_to_hour')
    if btc_to_hour is None:
        hours_worked_total = hours_per_machine.aggregate(Sum('hours_worked'))
        btc_to_hour = compute_hour_ratio(hours_worked_total['hours_worked__sum'])
        cache.set('btc_to_hour', btc_to_hour, 3600)  # Cache for an hour
    machine_turnover = hours_per_machine.annotate(turnover=F('hours_worked') * btc_to_hours)
    for machine in machine_turnover:
        # do something with machine.turnover
\$\endgroup\$
  • \$\begingroup\$ Thanks. Very informative and worked well. Last obstacle, I'm trying to cache the info hourly. Do you have advice? codereview.stackexchange.com/questions/180841/… \$\endgroup\$ – Josh Nov 20 '17 at 7:53
  • 1
    \$\begingroup\$ @Josh docs.djangoproject.com/en/1.11/topics/cache ? \$\endgroup\$ – Mathias Ettinger Nov 20 '17 at 7:59
  • 1
    \$\begingroup\$ @Josh Answer updated to include advices from the previous link \$\endgroup\$ – Mathias Ettinger Nov 20 '17 at 8:49
  • \$\begingroup\$ One question. If I have different users with different model fields ('hours_worked' etc.) will the cache refresh for every user or will it send cached result of previous user until hour up? Could I use vary_on_cookie if that is the case? \$\endgroup\$ – Josh Nov 20 '17 at 9:55
  • 1
    \$\begingroup\$ @Josh Using the low-level cache API, the cache is global (it will send cached result of previous user until hour up), you don't have much of the shortcut functions available either. But, in the view, you do have access to the request.COOKIES['sessionid'] set by the login view. So you can get that in a user_cookie variable and cache.get('btc_to_hour-'+user_cookie) if you want to. \$\endgroup\$ – Mathias Ettinger Nov 20 '17 at 10:05
3
\$\begingroup\$

totalMachineHoursWorked should be a @classmethod. It could be written more succinctly like this, using sum():

@classmethod
def totalMachineHoursWorked(cls, self):
    return sum(l3.indivMachineHoursWorked() for l3 in cls.objects.all())

However, I have doubts that any of the calculations should be done in Python. totalMachineHoursWorked() needs to fetch the entire table. I would recommend trying to find a way to do all of these calculations in SQL instead, because that's what SQL servers are designed to do: provide an efficient tool to let you extract aggregate reports by formulating your query intelligently.

In particular, if you need to calculate btcToHour() for multiple hosts, that would be horribly inefficient, since it would need to calculate totalMachineHoursWorked() multiple times, each of which involves fetching the entire table, and also performing a calculation on an individual row.

\$\endgroup\$
  • \$\begingroup\$ Thanks those were my thoughts too. It is calculation intensive but at the moment it seems to work. I will have to change it if it lags. I'll also have to research how to do SQL calculations from Django then (not just query_sets?) \$\endgroup\$ – Josh Nov 9 '17 at 7:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.