Custom save method giving invalid tuple size error - django

I've been stuck on this likely very simple problem, but haven't gotten anywhere with it (newbie to Python and Django). I'm taking some user submitted data and using weights to calculate a score. Despite my best efforts, I'm getting the following when I submit the data via a form: "global name 'appearance' is not defined". I'm pretty sure my issue is in views.py, but I'm not 100% sure. Either a typecast error or just putting the calculation of the score in the wrong place. Any help is much appreciated. Here's my code:
Update: The error I'm receiving after changing my approach to using a custom save method is: "Invalid tuple size in creation of Decimal from list or tuple. The list or tuple should have exactly three elements.".
models.py
# Beer rating weights
APPEARANCE_WEIGHT = 0.15
AROMA_WEIGHT = 0.15
MOUTHFEEL_WEIGHT = 0.10
TASTE_WEIGHT = 0.25
TOTALPACKAGE_WEIGHT = 0.25
SERVING_TYPE = (
('0', 'Choose One'),
('Draft', 'Draft'),
('Bottle', 'Bottle'),
('Can', 'Can'),
)
SCORING = (
(0, ''),
(1, '1'),
(2, '2'),
(3, '3'),
(4, '4'),
(5, '5'),
(6, '6'),
(7, '7'),
(8, '8'),
(9, '9'),
(10, '10'),
)
class Beerrating(models.Model):
beerrated = models.ForeignKey(Beer)
user = models.ForeignKey(User)
date = models.DateTimeField(auto_now_add=True)
servingtype = models.CharField(max_length=10, choices=SERVING_TYPE)
appearance = models.IntegerField(choices=SCORING, default=0)
aroma = models.IntegerField(choices=SCORING, default=0)
mouthfeel = models.IntegerField(choices=SCORING, default=0)
taste = models.IntegerField(choices=SCORING, default=0)
totalpackage = models.IntegerField(choices=SCORING, default=0)
comments = models.TextField()
overallrating = models.DecimalField(max_digits=4, decimal_places=2)
def __unicode__(self):
return u'%s, %s' % (self.user.username, self.beerrated.beername)
def save(self):
if not self.id:
scoredappearance = self.appearance * APPEARANCE_WEIGHT,
scoredaroma = self.aroma * AROMA_WEIGHT,
scoredmouthfeel = self.mouthfeel * MOUTHFEEL_WEIGHT,
scoredtaste = self.taste * TASTE_WEIGHT,
scoredtotalpackage = self.totalpackage * TOTALPACKAGE_WEIGHT,
self.overallrating = (scoredappearance + scoredaroma +
scoredmouthfeel + scoredtaste + scoredtotalpackage)
super(Beerrating, self).save()
forms.py
class BeerReviewForm(ModelForm):
servingtype = forms.CharField(max_length=10,
label=u'Serving Type',
widget=forms.Select(choices=SERVING_TYPE)
)
totalpackage = forms.IntegerField(
label=u'Total Package',
widget=forms.Select(choices=SCORING)
)
class Meta:
model = Beerrating
exclude = ('beerrated', 'user', 'date', 'overallrating')
views.py
def beerreview(request, beer_id):
beer = get_object_or_404(Beer, id=beer_id)
if request.method == 'POST':
form = BeerReviewForm(request.POST)
if form.is_valid():
# Create review
beerrating = Beerrating(
beerrated = beer,
user = request.user,
servingtype = form.cleaned_data['servingtype'],
appearance = form.cleaned_data['appearance'],
scoredappearance = appearance * APPEARANCE_WEIGHT,
aroma = form.cleaned_data['aroma'],
scoredaroma = aroma * AROMA_WEIGHT,
mouthfeel = form.cleaned_data['mouthfeel'],
scoredmouthfeel = mouthfeel * MOUTHFEEL_WEIGHT,
taste = form.cleaned_data['taste'],
scoredtaste = taste * TASTE_WEIGHT,
totalpackage = form.cleaned_data['totalpackage'],
scoredtotalpackage = totalpackage * TOTALPACKAGE_WEIGHT,
comments = form.cleaned_data['comments'],
)
beerrating.save()
return HttpResponseRedirect('/beers/')
else:
form = BeerReviewForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('beer_review.html', variables)

The error message should specifically tell you the file and line number of the error, but your problem are these two lines in your views.py:
appearance = form.cleaned_data['appearance'],
scoredappearance = appearance * APPEARANCE_WEIGHT,
You are assuming the Python interpreter computes the value for appearance before you use it in the next argument... which is an incorrect assumption.
Define appearance before you create the model instance and your code should then work (or at least break on a different error).

In your save method, the lines:
scoredappearance = self.appearance * APPEARANCE_WEIGHT,
...
are all assigning a tuple, not the number you expect, to the variables. A tuple is basically an immutable list. The training comma on all those lines makes them tuples. What you want is:
scoredappearance = self.appearance * APPEARANCE_WEIGHT
...
Two other problems with your save function. First, because of your indentation, your super only gets called on an update -- which means you'll never be able to create this object!
Secondly, I'd recommend adding the variable arg lists to your save function. This means if it gets called with parameters, they get transparently passed onto the super.
Here's the rewritten function:
def save(self,*args,**kwargs):
if not self.id:
scoredappearance = self.appearance * APPEARANCE_WEIGHT
scoredaroma = self.aroma * AROMA_WEIGHT
scoredmouthfeel = self.mouthfeel * MOUTHFEEL_WEIGHT
scoredtaste = self.taste * TASTE_WEIGHT
scoredtotalpackage = self.totalpackage * TOTALPACKAGE_WEIGHT
self.overallrating = (scoredappearance + scoredaroma +
scoredmouthfeel + scoredtaste + scoredtotalpackage)
super(Beerrating, self).save(*args,**kwargs)
As a final note -- and if you've already done this, I apologize -- I'd really recommending working through a book like Learning Python. Python's a generally straightforward language -- but it has some subtle features (like tuples and variable argument lists) that will cause you trouble if you don't understand them.

Related

How to calculate numbers of days between two dates and subtract weekends DJANGO MODELS

hope you're all fine!
I have a model called Vacation and I'm struggling with one field: days_requested, this field is the number days from vacation_start and vacation_end, it works and gives me an integer as result. The problem I'm facing now is that I need to subtract the weekends (or not count them).
What I have:
vacation_start = '2022-05-20'
vacation_end = '2022-05-24'
days_requested = 5
What I'm trying to have:
vacation_start = '2022-05-20'
vacation_end = '2022-05-24'
days_requested = 3
#21/05 and 22/05 are weekends
Vacation Models:
class Vacation(models.Model):
department = models.ForeignKey(
'departments.Department', on_delete=models.SET_NULL, null=True)
responsible = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, related_name='responsible_vacation')
status = models.CharField(
max_length=20, choices=STATUS_CHOICES_VACATIONS, default='open')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, related_name='created_by_vacation')
vacation_start = models.DateField(blank=False)
vacation_end = models.DateField(blank=False)
days_requested = property(
lambda self: (self.vacation_end - self.vacation_start).days + 1
)
def __str__(self):
return str(self.created_by.first_name + ' ' + self.created_by.last_name)
I have tried:
days_requested = property(
lambda self: [(self.vacation_start + datetime.timedelta(days=i)).date()
for i in range(0, (self.vacation_end - self.vacation_start)) if (self.vacation_start + datetime.timedelta(days=i)).weekday() not in [5, 6].days()])
But I get the following error:
'datetime.timedelta' object cannot be interpreted as an integer
And as I perform operations with the amount of days asked I need to be able to get an integer.
Thank you all in advance.
UPDATE
class Model:
days_requested = models.IntegerField(blank=True,null=True)
def save(self, *args, **kwargs):
excluded = (6, 7)
days = 0
start_date =self.vacation_start
while start_date < self.vacation_end:
if start_date.isoweekday() not in excluded: #if you want to get only weekdays
days += 1
start_date+= timedelta(days=1)
self.days_requested=days
super(YourModel, self).save(*args, **kwargs)
After Elvin's answer I moved the hole logic to my view and set the logic inside form_valid function:
start = form.instance.vacation_start
end = form.instance.vacation_end
delta = end - start
excluded = (6, 7)
days = 0
for i in range(delta.days + 1):
day = start + datetime.timedelta(days=i)
if day.isoweekday() not in excluded:
days += 1
form.instance.days_requested = days
Thank you all.

How do I use data stored in django model in a calculation and then store the result of a calculation in a django model field?

I'm working on my first Django app, and I need to take the data inputted by a user in my models fields, insert it into a function that makes a calculation using that data, and then returns the value of that calculation to my model where it is then stored.
It is not essential that the result be stored in my database, however I will need to use the resulting figure later on to allow the app to determine which data to present to the user.
I have my model class in models.py:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
age = models.IntegerField(default=18)
gender = models.CharField(max_length=6, choices=gend, default='')
weight = models.IntegerField(default=0)
height = models.IntegerField(default=0)
and my function in a services.py file:
def caloriefunction():
weight = Profile.weight
height = Profile.height
age = Profile.age
isMale = Profile.gender
if isMale == "Male":
isMale = True
elif isMale == "Female":
isMale = False
else:
print("Error")
quit()
if isMale:
bmr = 66.5 + (13.75 * weight) + (5 * height) - (6.755 * age)
else:
bmr = 655.1 + (9.6 * weight) + (1.8 * height) - (4.7 * age)
bmr = round(bmr)
return bmr
How would I get the resulting value and then store it in my database or keep it to use in another piece of logic?
Would I be better off using the function in the class as a method?
Sorry if the question isn't being asked very well i'm quite a newbie.
Any help if appreciated!
Yes, you can add that as a method of Profile. And/or if you want to keep the result handy you could add another field to the profile model, and override the save method so it is recalculated whenever an instance is saved. Something like this:
def save(self, *args, **kwargs):
self.update_bmr()
super().save(*args, **kwargs)
def update_bmr(self):
if self.gender == "Male":
self.bmr = (
66.5
+ (13.75 * self.weight)
+ (5 * self.height)
- (6.755 * self.age)
)
elif self.gender == "Female":
self.bmr = (
655.1
+ (9.6 * self.weight)
+ (1.8 * self.height)
- (4.7 * self.age)
)
else:
self.bmr = None
You may need to guard against missing data.

quantize result has too many digits for current context

Im trying to save the operation result in my admin.py and i have this error:
quantize result has too many digits for current context
....
def save_model(self, request, obj, form, change):
usuario_libra = obj.consignee.membresia.libra
valores = Valores.objects.get(pk=1)
vtasa = valores.tasa
vaduana = valores.aduana
vgestion = valores.gestion
vfee = valores.fee
vcombustible = valores.combustible
trans_aereo = obj.peso * usuario_libra * vtasa
aduana = (obj.peso * vaduana )*vtasa
fee_airpot = (obj.peso * vfee)*vtasa
combustible = (obj.peso * vcombustible)*vtasa
itbis = (trans_aereo+vgestion)*Decimal(0.16)
total = trans_aereo + vgestion + aduana + fee_airpot + combustible + itbis
if not obj.id:
obj.total = total
...
What this mean?, all my model fields are Decimal
Any help please
Thank you
I was able to solve this problem by increasing the the 'max_digits' field option.
class Myclass(models.Model):
my_field = models.DecimalField(max_digits=11, decimal_places=2, blank=True, null=True)
Be sure to make it large enough to fit the longest number you wish to save.
If that does not work may also need to set the precision larger by:
from decimal import getcontext
...
getcontext().prec = 11
See the python decimal documentation for full context parameters.

Django: Dynamic model field definition

Hi Stackoverflow people,
I have a model definition which is rather monotone, it includes fields for bins from 1 to 50.
For the proof of concept, I wrote it by hand, but there must be a better way to automate the model definition and to keep the code nice and tidy.
So far I did it the follow way:
class Bins(models.Model):
p1 = models.DecimalField(_('bin 1'), max_digits=6, decimal_places=2)
p2 = models.DecimalField(_('bin 2'), max_digits=6, decimal_places=2)
p3 = models.DecimalField(_('bin 3'), max_digits=6, decimal_places=2)
...
p50 = ...
On the Django wiki, I found a summary for dynamic model definitions, but its seems that it does not allow for loops in the definition:
I have tried the code below, but I get an error msg that MAX_BIN = 2 is an invalid syntax.
I understand the error that I can't iterated over the field like I tried.
Bins = type('Bins', (models.Model,), {
MAX_BIN = 50
for i in range(MAX_BIN):
name_sects = ["p", str(i)]
"".join(name_sects): model.DecimalField(_("".join([bin ', str(i)])),
max_digits=6, decimal_places=2)
})
Is such a dynamic class definition generally possible?
If so, what is the best way to define the class?
Thank you for your help!
in your current edition, your loop is inside the definition of a dict. that's not allowed. however, you could define your fields in a dict outside your call to type which works fine. something like
attrs = {
other_field = models.IntegerField(),
'__module__': 'myapp.models',
}
MAX_BIN = 50
for i in range(MAX_BIN):
name_sects = ["p", str(i)]
attrs["".join(name_sects)] = model.DecimalField(_("".join(['bin ', str(i)])),
max_digits=6, decimal_places=2)
Bins = type('Bins', (models.Model,), attrs)
A simple solution that solves the same issue:
class Bins(models.Model):
MAX_BIN = 50
for i in xrange(1, MAX_BIN + 1):
vars()['p' + str(i)] = models.DecimalField('bin ' + str(i),
max_digits=6, decimal_places=2)

Generating models when I create one model (for a calendar app)

I've set up several models, as you'll see shortly in my code. These models are Era, Year, Month, Day, and Hour.
I'd like for, when I create an Era, it creates a set of Years, which in turn create Months, which in turn create Days, which create Hours. I'm sure I can figure out most of this if someone could just help me with the methodology with say the Era to Year step.
The goal is then to have the CurrentTime model able to step through all the other models (like a clock basically).
In case you're wondering, I'm going to try and make a simple web based game off this, that's why the values are a bit different from a regular calendar!
So here's my models.py file:
from django.db import models
# Create your models here.
DAY_LENGTH = 24 #1 day is 24 hours
MONTH_LENGTH = 24 #1 month is 24 days
MONTH_CHOICES = (
(1, 'January'),
(2, 'Febuary'),
(3, 'March'),
(4, 'April'),
(5, 'May'),
(6, 'June'),
(7, 'July'),
(8, 'August'),
(9, 'September'),
(10, 'October'),
(11, 'November'),
(12, 'December'),
(13, 'Extravember'),
(14, 'Othertober'),
)
DAY_CHOICES = (
(1, 'Monday'),
(2, 'Tuesday'),
(3, 'Wednesday'),
(4, 'Thursday'),
(5, 'Friday'),
(6, 'Saturday'),
)
YEAR_LENGTH = MONTH_CHOICES.length #1 year contains
ERA_LENGTH = 9999 #1 era is 9999 years #one of every month
NUMBER_OF_BLOCKS = 6 #Number of discreet actions programmable for a day
BLOCK_LENGTH = DAY_LENGTH / NUMBER_OF_BLOCKS
class Era(models.Model):
#Era number
value = models.AutoField()
#Name of the Era
name = models.CharField(max_length=50)
def __unicode__(self):
return "Era of " + self.name
class Year(models.Model):
#Year number
value = models.PositiveSmallIntegerField(max_value=9999)
#Era the year belongs to
era = models.ForeignKey('Era')
length = YEAR_LENGTH
def __unicode__(self):
return "Year " + self.value + self.era
class Month(models.Model):
#Should return name of month
value = models.PositiveSmallIntegerField(
choices=MONTH_CHOICES)
#Year that the month fits into
year = models.ForeignKey(Year)
def __unicode__(self):
return self.value " of " + self.year
class Day(models.Model):
#Should give the name of the day
name = models.PositiveSmallIntegerField(
choices = DAY_CHOICES)
#Month that the day belongs to
month = models.ForeignKey('Month')
#Day number, dependant on month length
value = models.PositiveSmallIntegerField(
max_value = month.length)
def __unicode__(self):
return self.name + ", day " + self.value + " of " + self.month
class Hour(models.Model):
value = models.PositiveSmallIntegerField(
max_value = DAY_LENGTH)
day = models.ForeignKey('Day')
def __unicode__(self):
return self.value + ":00, " + self.day
class CurrentTime(models.Model):
hour = ForeignKey('Hour')
day = ForeignKey('Day')
month = ForeignKey('Month')
year = ForeignKey('Year')
era = ForeignKey('Era')
def __unicode__(self): #!!will only work for current config!
return self.hour
I'm sure it's pretty messy... But in any case, please help me figure out my code! And any other help you'd like to give me, I appreciate too!
Storing a datastructure in 6 tables is overkill. You would not store a string "abc" in three different tables. So something in your design is, in my opinion, wrong.
Try instead to write your problem as simple as possible and post it to along with your solution to it here at SO.
There are sure alternatives to your implementation and import this
>>> import this
...
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
...
If you want to - against any advice ;-) - use your above stated Models, override the save method of your models.