Browse files

Approved patches for fixing callable attributes on ToOneField and ToM…

…anyField. Thanks Donald Stufft and Valdimir Volodin for the fixes, and pennersr and sgarcez, for reports.

Closes #347
Closes #355
Closes #554
Closes #608
Closes #609
  • Loading branch information...
1 parent c38a667 commit 94791908c29fe46023e1e4302624b4d56a0372fb @issackelly issackelly committed Aug 11, 2012
Showing with 38 additions and 13 deletions.
  1. +2 −0 AUTHORS
  2. +17 −12 tastypie/fields.py
  3. +9 −1 tastypie/resources.py
  4. +10 −0 tests/core/tests/fields.py
View
2 AUTHORS
@@ -61,6 +61,8 @@ Contributors:
* Andrey Voronov (eyvoro) for fixing a typo in the AUTHORS file.
* D.B. Tsai (dbtsai) for a fix relating to ``detail_uri_kwargs``.
* maraujop for a patch adding to ``X-HTTP-Method-Override`` support.
+* Donald Stufft (dstufft) for patching ToOneField callable attributes
+* Vladimir Volodin (vvolodin) for patching ToManyField callable attributes
Thanks to Tav for providing validate_jsonp.py, placed in public domain.
View
29 tastypie/fields.py
@@ -619,21 +619,26 @@ def __init__(self, to, attribute, related_name=None, default=NOT_PROVIDED,
self.fk_resource = None
def dehydrate(self, bundle):
- attrs = self.attribute.split('__')
- foreign_obj = bundle.obj
+ foreign_obj = None
- for attr in attrs:
- previous_obj = foreign_obj
- try:
- foreign_obj = getattr(foreign_obj, attr, None)
- except ObjectDoesNotExist:
- foreign_obj = None
+ if isinstance(self.attribute, basestring):
+ attrs = self.attribute.split('__')
+ foreign_obj = bundle.obj
+
+ for attr in attrs:
+ previous_obj = foreign_obj
+ try:
+ foreign_obj = getattr(foreign_obj, attr, None)
+ except ObjectDoesNotExist:
+ foreign_obj = None
+ elif callable(self.attribute):
+ foreign_obj = self.attribute(bundle)
- if not foreign_obj:
- if not self.null:
- raise ApiFieldError("The model '%r' has an empty attribute '%s' and doesn't allow a null value." % (previous_obj, attr))
+ if not foreign_obj:
+ if not self.null:
+ raise ApiFieldError("The model '%r' has an empty attribute '%s' and doesn't allow a null value." % (previous_obj, attr))
- return None
+ return None
self.fk_resource = self.get_related_resource(foreign_obj)
fk_bundle = Bundle(obj=foreign_obj, request=bundle.request)
View
10 tastypie/resources.py
@@ -2067,7 +2067,15 @@ def save_m2m(self, bundle):
continue
# Get the manager.
- related_mngr = getattr(bundle.obj, field_object.attribute)
+ related_mngr = None
+
+ if isinstance(field_object.attribute, basestring):
+ related_mngr = getattr(bundle.obj, field_object.attribute)
+ elif callable(field_object.attribute):
+ related_mngr = field_object.attribute(bundle)
+
+ if not related_mngr:
+ continue
if hasattr(related_mngr, 'clear'):
# Clear it out, just to be safe.
View
10 tests/core/tests/fields.py
@@ -656,6 +656,16 @@ def test_dehydrate(self):
self.assertEqual(user_bundle.data['username'], u'johndoe')
self.assertEqual(user_bundle.data['email'], u'john@doe.com')
+ def test_dehydrate_with_callable(self):
+ note = Note.objects.get(pk=1)
+ bundle = Bundle(obj=note)
+
+ field_1 = ToOneField(UserResource, lambda bundle: User.objects.get(pk=1))
+ self.assertEqual(field_1.dehydrate(bundle), '/api/v1/users/1/')
+
+ field_2 = ToManyField(UserResource, lambda bundle: User.objects.filter(pk=1))
+ self.assertEqual(field_2.dehydrate(bundle), ['/api/v1/users/1/'])
+
def test_hydrate(self):
note = Note()
bundle = Bundle(obj=note)

0 comments on commit 9479190

Please sign in to comment.