Opened 18 years ago
Closed 17 years ago
#5903 closed (fixed)
DecimalField returns default value as unicode string.
| Reported by: | Owned by: | Brian Rosner | |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Keywords: | DecimalField | |
| Cc: | pigletto@… | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Until a model instance is saved Django returns a default DecimalField value as a unicode string. This creates an ambiguity as to the return value when it is unknown whether the instance was just created/saved or retrieved from the database.
Attachments (3)
Change History (17)
by , 18 years ago
| Attachment: | decimal-field-default.patch added |
|---|
by , 18 years ago
| Attachment: | decimal-field-default.2.patch added |
|---|
Overrides the base "get_default" behavior for decimal fields (fixed indent)
comment:1 by , 18 years ago
| Needs tests: | set |
|---|---|
| Triage Stage: | Unreviewed → Design decision needed |
I'll pass this through design decision -- seems like a fair enough request, yes?
by , 18 years ago
| Attachment: | decimal_field_default_5903.patch added |
|---|
Patch that adds decimal.Decimal to smart_unicode
comment:2 by , 18 years ago
| Cc: | added |
|---|---|
| Needs tests: | unset |
I think the problem is in force_unicode not in DecimalField.
I've added a patch with tests for this.
This way it works just like other numeric fields, eg. float.
comment:3 by , 18 years ago
| Triage Stage: | Design decision needed → Ready for checkin |
|---|
comment:4 by , 18 years ago
| Triage Stage: | Ready for checkin → Design decision needed |
|---|
We'll leave as a design decision thanks pigletto.
follow-up: 6 comment:5 by , 17 years ago
This seems to be rearing its head in more loudly recently. I have a default value of Decimal('0') on a models.DecimalField in a model and it is throwing a TypeError: a float is required in django.db.backends.util.format_number from BaseDatabaseOperations.value_to_db_decimal because the value is ran through force_unicode with strings_only=True which will do that type conversion on the Decimal object.
follow-up: 7 comment:6 by , 17 years ago
Replying to brosner:
What do you do to trigger the "float is required" type error? That seems highly inconsistent for a DecimalField since just yesterday I stumbled across the fact that floats are no longer allowed to be passed in as values to a DecimalField (see #8050). At any rate if it's new behavior it might be due to the same change as that one, but I don't think it's intended that floats be required anywhere here...only I can't recreate getting that exception for a field with a Decimal() default so I'm not sure how you are triggering it?
follow-up: 8 comment:7 by , 17 years ago
Replying to Karen Tracey <kmtracey@gmail.com>:
Given this model:
class Cart(models.Model):
desc = models.CharField(_("Description"), blank=True, null=True, max_length=10)
date_time_created = models.DateTimeField(_("Creation Date"),
default=datetime.datetime.now)
customer = models.ForeignKey(Customer, blank=True, null=True)
cache_shipping_total = models.DecimalField(max_digits=12, decimal_places=2,
default=Decimal("0"))
stale_shipping_total = models.BooleanField(default=True)
When you do:
>>> cart = Cart()
>>> cart.save()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/Users/brian/Code/loony/<ipython console> in <module>()
/Users/brian/svn/django/trunk/django/db/models/base.py in save(self=<class 'loony.blog.models.Cart'> instance)
272 control the saving process.
273 """
--> 274 self.save_base()
self.save_base = undefined
275
276 save.alters_data = True
/Users/brian/svn/django/trunk/django/db/models/base.py in save_base(self=<class 'loony.blog.models.Cart'> instance, raw=False, cls=<class 'loony.blog.models.Cart'>)
328 if not pk_set or not record_exists:
329 if not pk_set:
--> 330 values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)]
values = undefined
f = <django.db.models.fields.DecimalField object at 0x10d5cd0>
f.get_db_prep_save = <bound method DecimalField.get_db_prep_save of <django.db.models.fields.DecimalField object at 0x10d5cd0>>
raw = False
global getattr = undefined
self = undefined
f.attname = 'cache_shipping_total'
f.pre_save = <bound method DecimalField.pre_save of <django.db.models.fields.DecimalField object at 0x10d5cd0>>
global True = undefined
meta.local_fields = [<django.db.models.fields.AutoField object at 0x10d5d90>, <django.db.models.fields.DecimalField object at 0x10d5cd0>]
global isinstance = undefined
global AutoField = <class 'django.db.models.fields.AutoField'>
331 else:
332 values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields]
/Users/brian/svn/django/trunk/django/db/models/fields/__init__.py in get_db_prep_save(self=<django.db.models.fields.DecimalField object at 0x10d5cd0>, value=u'0')
230 def get_db_prep_save(self, value):
231 "Returns field's value prepared for saving into a database."
--> 232 return self.get_db_prep_value(value)
self.get_db_prep_value = <bound method DecimalField.get_db_prep_value of <django.db.models.fields.DecimalField object at 0x10d5cd0>>
value = u'0'
233
234 def get_db_prep_lookup(self, lookup_type, value):
/Users/brian/svn/django/trunk/django/db/models/fields/__init__.py in get_db_prep_value(self=<django.db.models.fields.DecimalField object at 0x10d5cd0>, value=u'0')
734 def get_db_prep_value(self, value):
735 return connection.ops.value_to_db_decimal(value, self.max_digits,
--> 736 self.decimal_places)
self.decimal_places = 2
737
738 def get_manipulator_field_objs(self):
/Users/brian/svn/django/trunk/django/db/backends/__init__.py in value_to_db_decimal(self=<django.db.backends.sqlite3.base.DatabaseOperations object at 0x70d5b0>, value=u'0', max_digits=12, decimal_places=2)
300 if value is None:
301 return None
--> 302 return util.format_number(value, max_digits, decimal_places)
global util.format_number = <function format_number at 0x702b70>
value = u'0'
max_digits = 12
decimal_places = 2
303
304 def year_lookup_bounds(self, value):
/Users/brian/svn/django/trunk/django/db/backends/util.py in format_number(value=u'0', max_digits=12, decimal_places=2)
122 """
123 Formats a number into a string with the requisite number of digits and
124 decimal places.
125 """
--> 126 return u"%.*f" % (decimal_places, value)
decimal_places = 2
value = u'0'
TypeError: a float is required
comment:8 by , 17 years ago
Replying to brosner:
> /Users/brian/svn/django/trunk/django/db/models/fields/__init__.py in get_db_prep_value(self=<django.db.models.fields.DecimalField object at 0x10d5cd0>, value=u'0') > 734 def get_db_prep_value(self, value): > 735 return connection.ops.value_to_db_decimal(value, self.max_digits, > --> 736 self.decimal_places) > self.decimal_places = 2 > 737
This is old code. Try updating to r8143 or later and see if that fixes it? I can't recreate the error on the current level. (I do still see the behavior in the original ticket description, though.)
comment:9 by , 17 years ago
Argh, ok, thanks Karen. You are right. I *really* need to go back to my old method of tracking trunk. My local version of Django was one revision behind :P Updating to trunk does indeed fix this problem I am having. FTR, this ticket does solve the problem I had, but it appears it was tackled in a different way.
follow-up: 11 comment:10 by , 17 years ago
AFAICT this currently applies not only to default values of DecimalFields but explicitly initialized string values as well:
>>> c = Cart(cache_shipping_total='2.50')
>>> c.cache_shipping_total
'2.50'
>>> c.save()
>>> c.cache_shipping_total
'2.50'
>>> c2 = Cart.objects.get(pk=c.pk)
>>> c2.cache_shipping_total
Decimal("2.50")
Also, the string value is not converted to a Decimal object on save() but only when re-querying the object from the database. Neither of the suggested patches fixes this behaviour.
Should I open a new ticket or update the summary of this one?
comment:11 by , 17 years ago
Jacob replied to akaihola on #django-dev:
that's been discussed ad-nauseum in the past. models don't
have__setattr__hooks for speed, so you can say
"model.intfield = "Fish""for all Django cares.
![...] it's a well-known problem, but one without a "good" fix.
I think model validation will probably help out here; I'd
imagine that a side-effect of validating a model would coerce
data to the right types.
comment:12 by , 17 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
| Triage Stage: | Design decision needed → Accepted |
comment:13 by , 17 years ago
Some discussion for future reference: http://groups.google.com/group/django-developers/browse_thread/thread/dc3ff17e3835c0c0
comment:14 by , 17 years ago
| Resolution: | → fixed |
|---|---|
| Status: | assigned → closed |
Overrides the base "get_default" behavior for decimal fields