Django Model field calculated from other fields - python

There are quite a few other questions here around this but none of them have helped.
I would like a yield field to be calculated by yield = starts / finishes fields. I'd like to graph it using Highcharts.
However when I add an object via Admin portal - it's setting yield = 0 and won't calculate.
models.py:
class wYield(models.Model):
starts = models.PositiveIntegerField(default=0)
finishes = models.PositiveIntegerField(default=0)
## ONE: i've tried the below:
yield_num = model.FloatField()
def save(self, *args, **kwargs):
if self.starts > 0:
self.yield = self.finishes / self.starts
else:
self.yield = 0
super(wYield, self).save(*args, **kwargs)
## TWO: i've tried this but then I don't know how to see it in the Admin view:
def _set_yield(self):
if self.starts > 0:
x = self.finishes / self.starts
else:
x = 0
return x
yield_num = property(_set_yield)
## THREE: I think this works the same as TWO
#property
def yield_num(self):
if self.starts > 0:
return self.finishes / self.starts
else:
return 0
admin.py:
from .models import wYield
class wYieldAdmin(admin.ModelAdmin):
list_display = ('starts', 'finishes', 'yield_num')
admin.site.register(wYield, wYieldAdmin)

Figured it out (w the help of AbhiP!) Python int typecasting was the main culprit. Stupid problem to have!
Below is what worked, and allowed me to not save the calculated field (but display it and make it act like a field in the model):
#property
def yield_num(self):
if self.starts > 0:
#needed the float() to cast it out of an int
return self.finishes / float(self.starts)
else:
return 0

Maybe you don't have to save it on the db every time as it can be calculated on the fly; otherwise you'd be wasting space. Not to mention duplicity in the db values.
Also, if tomorrow your logic changes for yield you will have full freedom to do it on to your lambda expressions. Here's one example:
Class wYieldAdmin(admin.ModelAdmin):
yield = lambda self: (self.finishes / self.starts) if self.starts > 0 else 0
yield.short_description = 'Yield'
list_display = ('starts', 'finishes', 'yield')

Class wYieldAdmin(admin.ModelAdmin):
yield = lambda self: (self.finishes / self.starts) if self.starts > 0 else 0
yield.short_description = 'You have a big head'
list_display = ('starts', 'finishes', 'yield')

Related

How to save compute results in Models without re-computing to each call?

Actually, I got a model, with theses functions:
def speed_score(self):
high_intensity_running_time = ((self.h_i_run_time * 100/self.training_length) * 8)
if self.h_i_average_speed < 15:
average_speed_score = 10
else:
average_speed_score = self.cross_multiplication(30, self.h_i_average_speed, 50)
final_speed_score = int(high_intensity_running_time + average_speed_score)
if final_speed_score < 0 or final_speed_score > 100:
final_speed_score = 0
return final_speed_score
def stamina_score(self):
if self.distance < 1000:
distance_score = 5
else:
distance_score = self.cross_multiplication(16, (self.distance / 1000), 40)
high_intensity_running_duration = self.cross_multiplication(500, self.h_i_run_time, 30)
print self.pace
if self.pace < 0.10:
pace_score = 5
else:
pace_score = self.cross_multiplication(0.36, self.pace, 30)
print pace_score
final_stamina_score = int(distance_score + high_intensity_running_duration + pace_score)
if final_stamina_score < 0 or final_stamina_score > 100:
final_stamina_score = 0
return final_stamina_score
def activity_score(self):
if self.running_time_ratio <= 0.10:
running_time_ratio_score = 10
else:
running_time_ratio_score = self.cross_multiplication(50, self.running_time_ratio, 50)
run_per_min = self.run_number / (self.training_length/60)
if run_per_min < 1:
run_number_ratio = 5
else:
run_number_ratio = self.cross_multiplication(5, run_per_min, 50)
final_activity_score = int(running_time_ratio_score + run_number_ratio)
if final_activity_score < 0 or final_activity_score > 100:
final_activity_score = 0
return final_activity_score
As you can see theses are very basics functions to compute score on a game. I call them in my serializer as following:
class SessionSerializer(serializers.ModelSerializer):
owner = NestedOwnerSerializer()
class Meta:
model = FieldPlayer
fields = ['id', 'date', 'owner', 'report_id', 'speed_score', 'stamina_score', 'activity_score']
But the fact is that every time I call theses functions, it run them and make them computing. This is not the behaviour that I need, because computing have to be done only in the first call.
I would like to store them in a models.IntegerField.
How can I properly do this ? I didn't found any example of this kind of usage in DRF's doc.
I'm totally new to Django and python in general. Do you have an exemple to illustrate this?
Let's start with saving these in a DB
I don't know where you want to store the scores, but let's assume it's in FieldPlayer
class FieldPlayer(models.Model):
speed_score = models.IntegerField() # or FloatField() etc
another_score = ...
def save_scores(self):
self.speed_score = calculate_speed_score(...)
self.another_score = calculate_another_score(...)
self.save()
Now everything the score changes, your game logic needs to save those, by calling FieldPlayer.save_scores()
Use those fields in the serializer
Then, in the serializer, instead of calculating those fields, you simply read them again from the database, like the other regular fields.
Cache the scores in memory for more performance
To improve performance, it is possible, after reading those values from the DB, to store them in a cache. Just remember to update the cache when the score is updated (e.g. when you call PlayerField.save_scores())
That way you don't hit the database with every request. The database is hit only to update the score and read it once until it is changed again.

How to avoid date overlaps in Django models?

I have a model:
class Dimension_Item(models.Model):
uq_dimension_item_id = MyCharField(max_length=1024, primary_key=True)
dimension_id = MyCharField(max_length=1024)
dimension_item_id = MyCharField(max_length=100)
name = MyCharField(max_length=255)
description = MyCharField(max_length=512, null = True)
start_date = models.DateField(default=date(1,1,1))
end_date = models.DateField(default=date(9999,12,31))
function for adding information to the model:
def addRows(in_args):
rq = in_args[0]
pk_l=[]
rows = rq['rows']
if type(rows).__name__ == 'dict':
dim = Dimension_Item(
name=rows['name'],
start_date=rows['start_date'],
end_date=rows['end_date']
)
dim.save()
pk_l.append(dim.dimension_id)
elif type(rows).__name__ == 'list':
for i in rows:
dim = Dimension_Item(
name=rows['name'],
start_date=rows['start_date'],
end_date=rows['end_date']
)
dim.save()
pk_l.append(dim.dimension_id)
else:
pass
return getRows(in_args, pk_l)
# return "success add row"
clauses.addMethod(addRows)
and function for modifying the model items:
def modifyRows(in_args):
pk_l=[]
rq = in_args[0]
rows = rq['rows']
if type(rows).__name__ == 'dict':
dim = Dimension_Item.objects.get(pk=rows['pk'])
for attr in rows:
if attr!='pk':
try:
setattr(dim, attr, rows[attr])
except KeyError:
pass
dim.save()
pk_l.append(dim.dimension_id)
elif type(rows).__name__ == 'list':
for i in rows:
dim = Dimension_Item.objects.get(pk=i['pk'])
for attr in i:
if i!='pk':
try:
setattr(dim, attr, i[attr])
except KeyError:
pass
dim.save()
pk_l.append(dim.dimension_id)
else:
pass
return getRows(in_args, pk_l)
# return "success modify"
clauses.addMethod(modifyRows)
I should check if start_date and end_date fields don't overlap other records in the database.
For example, I enter: 2.02.1988-3.07.1989. And if I already have the record with 2.07.1989 - 3.08.1990, I have to throw an exception about date overlapping.
How better can I do it?
I would override the save() method of your Dimension_Item model.
In your custom save() method you can implement your check for overlapping dates. If everything is OK, create the object. If not, just return nothing (or raise and error.)
The Django documentation explains it really good: https://docs.djangoproject.com/en/dev/topics/db/models/#overriding-model-methods
Here is some (untested) code to get you started:
class Dimension_Item(models.Model):
start_date = models.DateField(default=date(1,1,1))
end_date = models.DateField(default=date(9999,12,31))
def save(self, *args, **kwargs):
# get number of items that have an overlapping start date
dimension_items_overlapping_start = Dimension_Item.objects.filter(start_date__gte=self.start_date, start_date__lte=self.end_date).count()
# get number of items that have an overlapping end date
dimension_items_overlapping_end = Dimension_Item.objects.filter(end_date__gte=self.start_date, end_date__lte=self.end_date).count()
overlapping_dimension_items_present = dimension_items_overlapping_start > 0 or dimension_items_overlapping_end > 0
if overlapping_dimension_items_present:
return
else:
super(Dimension_Item, self).save(*args, **kwargs) # Call the "real" save() method.
class Dimension_Item(models.Model):
start_date = models.DateField(default=date(1,1,1))
end_date = models.DateField(default=date(9999,12,31))
def save(self, *args, **kwargs):
# check for items that have an overlapping start date
dimension_items_overlapping_start = Dimension_Item.objects.filter(start_date__gte=self.start_date, start_date__lte=self.end_date).exists()
# check for items that have an overlapping end date
dimension_items_overlapping_end = Dimension_Item.objects.filter(end_date__gte=self.start_date, end_date__lte=self.end_date).exists()
# check for items that envelope this item
dimension_items_enveloping = Dimension_Item.objects.filter(start_date__lte=self.start_date, end_date__gte=self.end_date).exists()
overlapping_dimension_items_present = dimension_items_overlapping_start or dimension_items_overlapping_end or dimenstion_items_enveloping
if overlapping_dimension_items_present:
return
else:
super(Dimension_Item, self).save(*args, **kwargs) # Call the "real" save() method.

If statement in Django not working in properly

I have a small project and I have been unable to get the following statement to work. Any help would be great. User inputs can either self.sale_head which is a $ value,if a $ value is not add a self.estimated_weight_hd is used to get the total weight, the code below should return a estimated_weight_total which can be used for the total sale price. All works when I add the estimated_weight_total manually. I am lost as why.
def calc_estimated_weight_total(self):
if self.sale_head <= 0:
amount = (self.number * self.estimated_weight_hd)
return amount
def save(self):
self.estimated_total_weight = self.calc_estimated_weight_total()
super(SaleNote, self).save()
Your code doesn't do what you want cause if self.sale_head > 0 nothing will return. From your description, I think your code should be somthing like:
def calc_estimated_weight_total(self):
if self.sale_head <= 0:
amount = (self.number * self.estimated_weight_hd)
return amount
else:
return something_only_you_know
def save(self):
self.estimated_total_weight = self.calc_estimated_weight_total()
super(SaleNote, self).save()

SimpleListFilter called twice and finally ?e=1

I have an admin page in django 1.4.3 [final]. We use CF cards in a lot of hardware, each card connects to a VPN. I have a function which determines whether the card is online or not depending on its last feedback written in database.
models.py:
class Card(models.Model):
...
last_feedback = models.DateTimeField(_('Last feedback'), null=True, blank=True)
...
def online_status(self):
nowtime = calendar.timegm(timezone.now().utctimetuple())
seen = calendar.timegm(self.last_feedback.utctimetuple())
diff = nowtime-seen
if (self.state == 1) and (diff < 300):
return '<div style="width:100%%; height:100%%; background-color:green; color:white;">online & production</div>'
elif (diff < 300):
return '<div style="width:100%%; height:100%%; background-color:orange;">online</div>'
else:
return "offline"
online_status.allow_tags = True
admin.py:
class OnlineFilter(admin.SimpleListFilter):
title = _("Online")
parameter_name = "online"
def lookups(self, request, model_admin):
return (("yes", "Yes"),
("no", "No"),
)
def queryset(self, request, queryset):
out = self.filter(request, queryset)
f = open("/tmp/list", "w")
f.write(str(out))
f.close()
return out
def filter(self, request, queryset):
if not self.value():
return queryset
else:
out = []
if self.value() == 'yes':
for i in queryset:
try:
if i.online_status() != "offline":
out.append(i)
except:
pass
elif self.value() == 'no':
for i in queryset:
try:
if i.online_status() == "offline":
out.append(i)
except:
pass
return out
class CardAdmin(admin.ModelAdmin):
list_filter = [ ... , OnlineFilter ]
and everytime I try to set the online filter, the file /tmp/list gets full with the right set of cards and then filled with the default list - as if the filter gets called twice. And the URL in my browser says ?e=1 instead of ?online=yes. If the filter is not set, it is called only once - giving only one set of cards in the file.
BTW: the OnlineFilter.filter method is out of the queryset method just so that I know what goes out of my code, in final release I would have put it in only one method ...
is this a bug? or a feature? Am I doing something wrong? If so, what?
I wouldn't be surprised if django calls the queryset function twice - I seem to remember noticing that once before. Not sure why though. Regarding why the filter is not working, django expects a QuerySet to be returned, not a list. You might be able to construct a query to determine online status, but given the complexity I suspect it will prove to be beyond the powers to the django ORM and you'd need to revert to raw SQL. The alternative would be to create a list of primary keys, the simply filter the queryset by those, so:
def filter(self, request, queryset):
if not self.value():
return queryset
else:
pks = []
if self.value() == 'yes':
for i in queryset:
try:
if i.online_status() != "offline":
pks.append(i.pk)
except:
pass
elif self.value() == 'no':
for i in queryset:
try:
if i.online_status() == "offline":
pks.append(i.pk)
except:
pass
out = queryset.filter(pk__in=pks)
return out

Django/Python DRY: how to avoid repeat code when working with Q objects and model attributes

I'm trying to build a search page that will allow a user to find any instances of a model which meet certain threshold criteria and am having trouble avoiding seriously redundant code. I'm hoping there's a better way to do it. Here's a slightly contrived example that should illustrate what I'm trying to do, with the relevant code adjusted at the end. The user will interact with the search with checkboxes.
models.py:
class Icecream(models.Model()):
name = models.CharField()
bad_threshold = models.IntegerField()
okay_threshold = models.IntegerField()
tasty_threshold = models.IntegerField()
delicious_threshold = models.IntegerField()
views.py:
def search_icecreams(request):
user = request.user
q_search = None
if 'taste_search' in request.GET:
q_search = taste_threshold_set(request, user, q_search)
if q_search == None:
icecream_list = Icecream.objects.order_by('name')
else:
icecream_list = College.objects.filter(q_search)
context = { 'icecream_list' : icecream_list }
return render(request, '/icecream/icecreamsearch.html', context)
The relevant code that I want to cut down is as follows, this is pretty much straight from my project, with the names changed.
def taste_threshold_set(request, user, q_search):
threshold = request.GET.getlist('taste_search')
user_type_tolerance = user.profile.get_tolerance_of(icea
# 1-5 are the various thresholds. They are abbreviated to cut down on the
# length of the url.
if '1' in threshold:
new_q = Q(bad_threshold__gt = user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
if '2' in threshold:
new_q = Q(bad_threshold__lte=user.profile.taste_tolerance) & \
~Q(okay_threshold__lte=user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
if '3' in threshold:
new_q = Q(okay_threshold_3__lte=user.profile.taste_tolerance) & \
~Q(tasty_threshold__lte=user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
if '4' in threshold:
new_q = Q(tasty_threshold__lte=user.profile.taste_tolerance) & \
~Q(delicious_threshold__lte=user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
if '5' in threshold:
new_q = Q(delicious_threshold__lte = user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
return q_search
Basically I want the user to be able to find all instances of a certain object which meet a given threshold level. So, for example, all icecreams that they would find bad and all icecreams that they would find delicious.
There are a number of things I'm not happy with about this code. I don't like checking to see if the Q object hasn't been instantiated yet for each possible threshold, but don't see a way around it. Further, if this were a non-django problem I'd use a loop to check each of the given thresholds, instead of writing each one out. But again, I'm not sure how to do that.
Finally, the biggest problem is, I need to check thresholds for probably 20 different attributes of the model. As it stands, I'd have to write a new threshold checker for each one, each only slightly different than the other (the name of the attribute they're checking). I'd love to be able to write a generic checker, then pass it the specific attribute. Is there any way to solve this, or my other two problems?
Thanks!
How about this approach?
query_arg = ['bad_threshold__lte', 'bad_threshold__lte', 'okay_threshold_3__lte', 'tasty_threshold__lte', 'delicious_threshold__lte']
Q(**{query_arg[int(threshold) - 1]: user.profile.taste_tolerance})
You should use own QuerySet for the models instead def taste_threshold_set(...)
Example:
models.py:
...
from django.db.models.query import QuerySet
...
class IcecreamManager(models.Manager):
def get_query_set(self):
return self.model.QuerySet(self.model)
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
return getattr(self.get_query_set(), attr, *args)
class Icecream(models.Model()):
name = models.CharField()
bad_threshold = models.IntegerField()
okay_threshold = models.IntegerField()
tasty_threshold = models.IntegerField()
delicious_threshold = models.IntegerField()
objects = IcecreamManager()
class QuerySet(QuerySet):
def name_custom_method(self, arg1, argN):
# you must rewrite for you solution
return self.exclude(
time_end__gt=now()
).filter(
Q(...) | Q(...)
)
def name_custom_method2(...)
...
These should give you abilities build of chains querys for your issues.

Categories