Django ValueError: could not convert string to float - django

Even though I put this line in my settings.py :
LANGUAGE_CODE = 'pt-br'
TIME_ZONE = 'America/Sao_Paulo'
USE_I18N = True
USE_L10N = True
USE_TZ = True
DECIMAL_SEPARATOR = ','
DATE_INPUT_FORMATS = ['%d/%m/%Y']
DATE_FORMAT = r'd/m/Y'
As specified here : https://docs.djangoproject.com/en/1.10/ref/settings/#decimal-separator
Even with L10N set to False it won't recognize(although the language code should already set decimal separator as comma)
Django still won't recognize the comma as decimal separator
Actual error :
ValueError: could not convert string to float: '123,123'
The field is just a default FloatField, I'm not using forms.
What could be causing it to not recognize the comma?
This is the views.py code :
def new_object(request):
data = json.loads(request.body.decode("utf-8"))
model_name = data.get('model')
model = apps.get_model(app_label='cadastroimoveis', model_name=model_name)
obj = model(**data.get('fields'))
obj.save()
The sent request is just a JSON with the fields as strings
Edit: I just checked and not even the DATE_INPUT_FORMATS it working, it is still expecting the default values

The problem is you seem to be confusing model fields with form fields. The form field offers localization, and it works:
>>> from django.db.models.fields import FloatField
>>> from django.forms.fields import FloatField as FloatFormField
>>> model_field = FloatField()
>>> form_field = model_field.formfield(localize=True)
>>> isinstance(form_field, FloatFormField)
True
>>> form_field.to_python('123,123')
123.123
The model field does not:
>>> model_field.to_python('123,123')
ValidationError: [u"'123,123' value must be a float."]
The model field is nowhere documented as supporting localization, and I can see nothing in the source to suggest that it should be supported.
The line obj = model(**data.get('fields')) shows that you are not using forms at all, you're just consuming a JSON data source and ramming that directly into the model. So, I think the better option for you is to pre-process the JSON data, since Django doesn't seem to support what you're looking for.
You might like to use sanitize_separators helper function, since that's what the forms.fields.FloatField uses to clean the data. Demo:
>>> from django.utils.formats import sanitize_separators
>>> sanitize_separators('123,123')
'123.123'

This seems a Django bug and there is something similar reported here
But I'm pretty sure you can overcome this by using forms. I believe, when you try to update that FloatField value in your admin you see : '123.123' and this is because the value is kept in DB's numeric field which cannot support ',' comma. But you can use forms and define form field as follows to see comma instead:
your_field_name = forms.FloatField(localize=True)
By saying localize=True it does 2 things; one to use TextInput instead of NumberInput and also force Django to format the number based on localize settings - doc.
But if you don't use forms, the only way to overcome this is to sanitize you numbers before assigned to model field, and you can use formats.sanitize_separators() function from django.utils:
from django.utils import formats
model.your_field_name = formats.sanitize_separators(value_from_user)
model.save()

Related

Django Admin Model Thousand Separator in Change form when using Grappelli extension

I am working on an internal Django project using the Grappelli Admin Theme. I am in Australia and would like to format decimal numbers above 999 as 1,000.00 2,000.00. I have added the following to my settings.py file and the Model field has been defined as a Decimal.
The Change List form displays the number correctly formatted. However, the changeform does not and retains the unlocalized format.
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Australia/Queensland'
USE_I18N = True
USE_L10N = True
USE_THOUSAND_SEPARATOR = True
NUMBER_GROUPING = 3
I have found numerous similar posts such as Make Django forms use comma as decimal separator
And it does state
localization does need to be turned on explicitly for every field. For a model form (including the ones used in the admin app), a convenient way to do this is to subclass ModelForm and turn on localization for each DecimalField
However, at this stage I just need the Admin Change Form to carry the localisation and I am not sure how to do this when the Grappelli extension is in play. And as I have mentioned the Change List Form does render the same field with the thousand separator as I had hoped.
Your thoughts would be appreciated.

I can not change the date format in django drf

I have drf model which is containe DateField. That field default format is "YYYY-MM-DD" just i want to convert "DD-MM-YYYY" how can is possible.
from rest_framework import serializers
from.models import SpaUser
from djoser.serializers import UserCreateSerializer as BaseUserRegistrationSerializer
import datetime
from rest_framework.settings import api_settings
class SpaUserSerializer(serializers.ModelSerializer):
date_joined = serializers.ReadOnlyField()
birthdate = serializers.DateField(format="%d-%m-%Y", input_formats=['%d-%m-%Y',])
If it is universal, in your settings file add "DATE_INPUT_FORMATS" to REST_FRAMEWORK settings like:
REST_FRAMEWORK = {
"DATE_INPUT_FORMATS": ["%d-%m-%Y"],
...
}
for more details check http://www.django-rest-framework.org/api-guide/settings/#date-and-time-formatting
To have it correctly work, input_formats is the argument you need to assign the format needed, format is the output format
birthdate = serializers.DateField(input_formats=['%d-%m-%Y',])
or you can set the default input format in your settings
DATE_INPUT_FORMATS = [
("%d-%m-%Y"),
]
By combining all your solutions, it works
REST_FRAMEWORK = {
# "DATE_INPUT_FORMATS": "%d-%m-%Y", doesn't works
'DATE_INPUT_FORMATS': [("%d-%m-%Y"),],it works
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
}
From DRF documentation regarding DateField
Format strings may either be Python strftime formats which explicitly
specify the format, or the special string 'iso-8601', which indicates
that ISO 8601 style dates should be used. (eg '2013-01-29')
So in your case format should be
format="%d-%m-%Y"
Although this thread is quite old, I found it when trying to solve a similar problem. Although the responses didn't give me the entire answer, they did push me to simply read DRF's quite helpful documentation on the topic:
https://www.django-rest-framework.org/api-guide/settings/#date-and-time-formatting
In my case, I am building a events calendar using Vuetify's v-calendar, Vue JS, and DRF. The v-calendar requires the format YYYY-MM-DD HH:MM, and prefers a string. I could simply store the string in my DB, but I preferred to store the datetime fields in native Python formatting for server-side processing should I ever need them. So, in my settings.py file, I added the following to my REST_FRAMEWORK setting:
'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M',],
'DATETIME_FORMAT': '%Y-%m-%d %H:%M',
The first one takes a list (hence the brackets and comma), the second just takes a string. Then, in my serializer, I use:
start = serializers.DateTimeField()
On the front end, I take in the dates as the string objects, pass them to the calendar for display, and it works. When POSTing data, I simply pass them back in that same string format, and the DRF serializer encodes them as native Python for storage.
We have to differentiate between all these:
DATE_INPUT_FORMATS
To specify the date format for the input in POST request API.
DATETIME_FORMAT
To specify the DateTime format for serialized data like with GET request.
DATE_FORMAT
To specify the Date format for serialized data like with GET request.
And this is an example of how to use these settings in the settings.py file.
REST_FRAMEWORK = {
'DATE_INPUT_FORMATS': ["%Y-%m-%d %H", ],
'DATETIME_FORMAT': '%Y-%m-%d %H',
'DATE_FORMAT': '%Y-%m-%d %H'
}
Note: You should use these settings just if you want your serializer to have the same data or DateTime format.
But in case you have changed in some field you can just change the format for that field and here is an example of how to do like this:
class YourSerializer(serializers.ModelSerializer):
...
def to_representation(self, instance):
representation = super(YourSerializer, self).to_representation(instance)
representation['created_at'] = instance.created_at.strftime('%Y-%m-%d %H)
return representation
Or in a similar way
class YourSerializer(serializers.ModelSerializer):
created_at = serializers.DateTimeField(format='%Y-%m-%d %H')
In settings.py include:
REST_FRAMEWORK = {
'DATE_FORMAT': '%d-%m-%Y'
}

django ModelForm doesn't seem to validate BooleanField

I'm using django 1.5.4
Here's a minimal example of the issue I'm facing.
The model:
#models.py
from django.db import models
class SampleModel(models.Model):
spam = models.BooleanField()
The form:
#forms.py
from django.forms import ModelForm
from .models import SampleModel
class SampleModelForm(ModelForm):
class Meta:
model = SampleModel
fields = ('spam', )
From the django shell:
>>> data = {} #intentionally blank
>>> form = SampleModelForm(data)
>>> is_valid = form.is_valid() #is_valid is True
>>> form.save() # model instance is created with "spam" set to False by default.
Am I validating the form incorrectly? form.is_valid validates fields of other types correctly.
The docs indicate that all fields are required by default but is_valid returns Truewithout the boolean field key being present.
I need to ensure that the boolean field is present in the input data.
As of now, I'm manually checking if the field is present and is of type bool. Do you think I should override form.is_valid and add this check so that it can be reused for other models too?
It turns out (from code inspection; the docs don't say) that model BooleanFields have blank=True set automatically in their __init__ method, thus making the automatically created model form field not required. This makes sense upon consideration (False counts as blank, so BooleanFields need it to be true) but it's not obvious when you just read the docs.
If you want it to be required to be True, the usual form field overrides apply - declare the field yourself or set its required attribute to be True somewhere before validating (I usually use the form's __init__ method). If you want it to allow True or False but not Python None, it's harder.
Specifically, the standard widget for a BooleanField is a checkbox. Browsers do not submit anything for unchecked checkboxes, so the checkbox widget treats the absence of the field from the submit as False. There's no way to distinguish the user not selecting the checkbox from cases in which the input really is bad. You could use a different widget (say, a RadioSelect) to at least make it possible for the browser to submit something for False, but you still have the problem that the BooleanField's to_python method converts its value to a boolean before validating, so you'd have to subclass and override.
Fortunately, False is not considered empty by the validation code, so if you do that you'll be able to set required=True and don't need custom cleaning methods.

Using django fields validation for external values

I have model with one field(this is synthetic example):
model Tank:
oxygen = models.PositiveSmallIntegerField(
_("Oxygen %"),
help_text="%",
default=21,
validators=[MinValueValidator(21.0), MaxValueValidator(50.0)],
null=True,
)
And I parse some files with data. I want to validate input data before write it model instance. Something like this
oxygen = get_raw_data()
Tank.oxygen.validate(oxygen) # this is wrong I know :)
# if value is valid do something
# else do something
What should I write instead of Tank.oxygen.validate(oxygen)?
I can duplicate validation logic or validate data when save model instance, but maybe somebody know better solution.
You need to actually create an instance with the data, then call full_clean() on it:
my_tank = Tank(oxygen=oxygen)
my_tank.full_clean()
If you only want to validate one field, then I suggest you use a form.Field class to do it:
from django import forms
from django.core.exceptions import ValidationError
oxygen_field = forms.IntegerField(required=True, min_value=21, max_value=50)
def is_oxygen_valid(value):
try:
oxygen_field.clean(value)
except ValidationError:
return False
else:
return True
Testing:
>>> is_oxygen_valid(None)
False
>>> is_oxygen_valid(11)
False
>>> is_oxygen_valid(55)
False
>>> is_oxygen_valid('hello')
False
>>> is_oxygen_valid(list())
False
>>> is_oxygen_valid(45)
True
I'm assuming that you are going to be validating the oxygen field first, and then deciding what to do depending on the result. You could adapt this to any other field you need. Just find the appropriate FormField class, and any Validators you might need, and use that instead.

How to get a Django admin form field for a models.DecimalField with value 0 not to use E notation

We use django admin as a client-facing backend, so we need to make it user friendly. I have a model with a bunch of DecimalFields representing nutritional data.
The fields all look like this:
g_carbs = DecimalField(max_digits=13, decimal_places = 8, null=True, blank=True)
If the field is left blank, or if a non-zero value is provided, the admin form looks and works great. For example, for a non-blank, non-zero value like 10.5, it displays something like 10.50000000, which is fine.
The problem is that for any 0 values, the form field displays 0E-8 which, although technically correct, is not going to cut it for my clients, who are definitely not scientists or engineers for the most part and are unfamiliar with E notation.
I am not using a custom form or any custom admin tricks. Its just what gets auto-rendered by django admin for that model. I'm considering submitting a ticket to django for this, but in the mean time is there something I can do with a custom form or something to remedy this problem?
Here's what ended up working for me (so far). This both prevents E notation and removes trailing 0's after the decimal point.
class NonscientificDecimalField(DecimalField):
""" Prevents values from being displayed with E notation, with trailing 0's
after the decimal place truncated. (This causes precision to be lost in
many cases, but is more user friendly and consistent for non-scientist
users)
"""
def value_from_object(self, obj):
def remove_exponent(val):
"""Remove exponent and trailing zeros.
>>> remove_exponent(Decimal('5E+3'))
Decimal('5000')
"""
context = decimal.Context(prec=self.max_digits)
return val.quantize(decimal.Decimal(1), context=context) if val == val.to_integral() else val.normalize(context)
val = super(NonscientificDecimalField, self).value_from_object(obj)
if isinstance(val, decimal.Decimal):
return remove_exponent(val)
One simple way might be to subclass DecimalField and change its formatting.
from django.db.models.fields import DecimalField
class NonscientificDecimalField(DecimalField):
def format_number(self, value):
"""
Overrides DecimalField's usual format_number by making sure
that the result is never in exponential notation for zero.
"""
if value == 0:
return "0.00000000"
else:
return super(DecimalField, self).format_number(value)
You shouldn't submit this as a Django bug, by the way - this is the way Python decimals work and has little to do with Django. Open up a shell and try str(Decimal("0.00000000")) and you'll see Decimal('0E-8').
If you'd like to get 10.50000000 to show up as 10.5, you can call normalize on your decimals. This will also fix your 0E-8 problem:
import decimal
from django.db.models.fields import DecimalField
class NonscientificDecimalField(DecimalField):
def format_number(self, value):
"""
Overrides DecimalField's usual format_number to remove trailing zeroes.
"""
if isinstance(value, decimal.Decimal):
context = decimal.Context(prec=self.max_digits)
value = value.normalize(context=context)
return super(DecimalField, self).format_number(value)
It seems that there has been a fix for it: https://code.djangoproject.com/ticket/19220 but I am not sure if it fully works because I am using django 1.6.5 and the admin displays 0E-8.00000000 or 0E-8 for 0.
models.DecimalField(max_digits=12, decimal_places=8, null=True)
#Ben Roberts: Do you still have the issue?
Thanks