dominno / django-moderation

django-moderation is reusable application for Django framework, that allows to moderate any model objects.
BSD 3-Clause "New" or "Revised" License
269 stars 90 forks source link

moderation crashes on models with DecimalField #50

Closed mbrochh closed 4 years ago

mbrochh commented 12 years ago

I'm trying to moderate the creation of a django-shop Product model. This model has a field 'unit_price', which is a CurrencyField, which is just a fancy DecimalField (see https://github.com/divio/django-shop/blob/master/shop/util/fields.py#L6)

When I create a new product, I get the following error:

Traceback: File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/core/handlers/base.py" in get_response

  1. response = middleware_method(request, callback, callback_args, callback_kwargs) File "/home/martin/Envs/marketto/src/ajaxmiddleware/ajaxmiddleware/middleware.py" in process_view
  2. return new_callback(request, _callback_args, *_callback_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/views/generic/base.py" in view
  3. return self.dispatch(request, _args, *_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/utils/decorators.py" in _wrapper
  4. return bound_func(_args, *_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
  5. return view_func(request, _args, *_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/utils/decorators.py" in bound_func
  6. return func(self, _args2, *_kwargs2) File "/home/martin/Projects/aquasys/marketto/vivitz/vehicle_shop/views/crud.py" in dispatch
  7. return super(VehicleCreateView, self).dispatch(_args, *_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/views/generic/base.py" in dispatch
  8. return handler(request, _args, *_kwargs) File "/home/martin/Envs/marketto/src/ajaxmiddleware/ajaxmiddleware/views.py" in post
  9. return super(HybridView, self).post(self, **kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/views/generic/edit.py" in post
  10. return super(BaseCreateView, self).post(request, _args, *_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/views/generic/edit.py" in post
  11. return self.form_valid(form) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/views/generic/edit.py" in form_valid
  12. self.object = form.save() File "/home/martin/Projects/aquasys/marketto/vivitz/vehicle_shop/forms.py" in save
  13. return super(VehicleForm, self).save() File "/home/martin/Envs/marketto/lib/python2.6/site-packages/shop_simplecategories/admin.py" in save
  14. product.save() File "/home/martin/Projects/aquasys/marketto/vivitz/vehicle_shop/models.py" in save
  15. super(Vehicle, self).save(_args, *_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/polymorphic/polymorphic_model.py" in save
  16. return super(PolymorphicModel, self).save(_args, *_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/db/models/base.py" in save
  17. self.save_base(using=using, force_insert=force_insert, force_update=force_update) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/db/models/base.py" in save_base
  18. created=(not record_exists), raw=raw, using=using) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/dispatch/dispatcher.py" in send
  19. response = receiver(signal=self, sender=sender, **named) File "/home/martin/Envs/marketto/src/moderation/src/moderation/register.py" in post_save_handler
  20. moderator.inform_moderator(instance) File "/home/martin/Envs/marketto/src/moderation/src/moderation/moderator.py" in inform_moderator
  21. recipient_list=MODERATORS) File "/home/martin/Envs/marketto/src/moderation/src/moderation/moderator.py" in send
  22. 'moderated_object': content_object.moderated_object, File "/home/martin/Envs/marketto/src/moderation/src/moderation/register.py" in get_moderated_object
  23. '_relation_object').get() File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/db/models/manager.py" in get
  24. return self.get_query_set().get(_args, *_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/db/models/query.py" in get
  25. num = len(clone) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/db/models/query.py" in len
  26. self._result_cache = list(self.iterator()) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/db/models/query.py" in iterator
  27. obj = model(*row[index_start:aggregate_start]) File "/home/martin/Envs/marketto/src/moderation/src/moderation/models.py" in init
  28. super(ModeratedObject, self).init(_args, *_kwargs) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/db/models/base.py" in init
  29. signals.post_init.send(sender=self.class, instance=self) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/dispatch/dispatcher.py" in send
  30. response = receiver(signal=self, sender=sender, **named) File "/home/martin/Envs/marketto/src/moderation/src/moderation/fields.py" in post_init
  31. self._deserialize(value)) File "/home/martin/Envs/marketto/src/moderation/src/moderation/fields.py" in _deserialize
  32. for parent in obj_generator: File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/core/serializers/json.py" in Deserializer
  33. for obj in PythonDeserializer(simplejson.load(stream), **options): File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/core/serializers/python.py" in Deserializer
  34. data[field.name] = field.to_python(field_value) File "/home/martin/Envs/marketto/lib/python2.6/site-packages/django/db/models/fields/init.py" in to_python
  35. return decimal.Decimal(value) File "/usr/lib/python2.6/decimal.py" in new
  36. "First convert the float to a string")

Exception Type: TypeError at /shop/products/create/ Exception Value: Cannot convert float to Decimal. First convert the float to a string

I really have no clue why there is a Float involved at all... Are there known issues with DecimalFields?

mbrochh commented 12 years ago

After hours of debugging I found out the following:

At this line (https://github.com/dominno/django-moderation/blob/master/src/moderation/fields.py#L48) you call the JSON deserializer.

Django will then call this line (https://github.com/django/django/blob/master/django/core/serializers/json.py#L35):

for obj in PythonDeserializer(simplejson.load(stream), **options):

It calls the PythonDeserializer. Since that deserializer expects to get a list of Python objects, Django tries to convert our JSON string via simplejson.load(stream) which, by default, will try to deserialize "0.00" into a float. If we called simplejson.load(stream, use_decimal=True), all would be good.

It is totally beyond me why the Django devs did not accept two option dicts, one for the simplejson.load call and one for the PythonDeserializer call.

So, unless there is another generic way to tell Django to use use_decimal for simplejson.load calls, I think, we must not

use obj_generator = serializers.deserialize(self.serialize_format, value.encode(settings.DEFAULT_CHARSET))

Instead we would need our own deserializer who calls

for obj in PythonDeserializer(simplejson.load(stream), **options): yield obj

but allows kwargs for the simplejson.load call.

Hope that makes sense.

dominno commented 12 years ago

Yes, that make sense. I will make a fix this. Thank you very much for finding this.

dominno commented 12 years ago

I can't reproduce this error. I tried following:

My model is:

class ModelWithDecimal(models.Model):
    cost = CurrencyField()

Then i did:

obj = ModelWithDecimal.objects.create(cost='20.00')

from moderation.fields import SerializedObjectField
json_field = SerializedObjectField()

json_field._serialize(obj)
Out[0]: '[{"pk": 1, "model": "test_app1.modelwithdecimal", "fields": {"cost": "20.00"}}]'

json_field._deserialize('[{"pk": 1, "model": "test_app1.modelwithdecimal", "fields": {"cost": "20.00"}}]')
Out[0]: <ModelWithDecimal: ModelWithDecimal object>

Could you show me how do you create your object ? And please show me what is the output of json_field._serialize(your_object)

I tested this with python 2.6, django 1.2 and 1.3.

mbrochh commented 12 years ago

Hmpf. Weird. I will have little time this week. I will try to look into this by Sunday...

caseycesari commented 12 years ago

+1. Experiencing the same issue.

dominno commented 12 years ago

@caseypt Could you give more info, how your serialized object look like ? How your model look like ?

DmytroLitvinov commented 4 years ago

Closed because of inactivity. Please feel free to open new issue.