編集 #2:最終的に問題を修正する方法を見つけましたが、残念ながら、サブクラス化とオーバーライドが少し必要です。これが私がそれを機能させた方法です:
まず、新しいフィールド サブクラスを作成します - 私は自分の RelatedToOneField を呼び出しました:
from tastypie.bundle import Bundle
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from tastypie.exceptions import ApiFieldError, NotFound
class RelatedToOneField(fields.RelatedField):
"""
Provides access to related data via foreign key.
This subclass requires Django's ORM layer to work properly.
"""
help_text = 'A single related resource. Can be either a URI or set of nested resource data.'
def __init__(self, to, attribute, related_name=None, default=fields.NOT_PROVIDED,
null=False, blank=False, readonly=False, full=False,
unique=False, help_text=None):
super(RelatedToOneField, self).__init__(
to, attribute, related_name=related_name, default=default,
null=null, blank=blank, readonly=readonly, full=full,
unique=unique, help_text=help_text
)
self.fk_resource = None
def dehydrate(self, bundle):
try:
foreign_obj = getattr(bundle.obj, self.attribute)
except ObjectDoesNotExist:
foreign_obj = None
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." % (bundle.obj, self.attribute))
return None
self.fk_resource = self.get_related_resource(foreign_obj)
fk_bundle = Bundle(obj=foreign_obj, request=bundle.request)
return self.dehydrate_related(fk_bundle, self.fk_resource)
def hydrate(self, bundle):
value = super(RelatedToOneField, self).hydrate(bundle)
if value is None:
return value
# START OF MODIFIED CONTENT
kwargs = {
'request': bundle.request,
}
if self.related_name:
kwargs['related_obj'] = bundle.obj
kwargs['related_name'] = self.related_name
return self.build_related_resource(value, **kwargs)
#return self.build_related_resource(value, request=bundle.request)
#END OF MODIFIED CONTENT
次に、「最上位」モデル (この場合は UserResource) で obj_create および save_related 関数をオーバーライドします。関連するオーバーライドは次のとおりです。
def obj_create(self, bundle, request=None, **kwargs):
"""
A ORM-specific implementation of ``obj_create``.
"""
bundle.obj = self._meta.object_class()
for key, value in kwargs.items():
setattr(bundle.obj, key, value)
bundle = self.full_hydrate(bundle)
# Save the main object.
# THIS HAS BEEN MOVED ABOVE self.save_related().
bundle.obj.save()
# Save FKs just in case.
self.save_related(bundle)
# Now pick up the M2M bits.
m2m_bundle = self.hydrate_m2m(bundle)
self.save_m2m(m2m_bundle)
return bundle
def save_related(self, bundle):
"""
Handles the saving of related non-M2M data.
Calling assigning ``child.parent = parent`` & then calling
``Child.save`` isn't good enough to make sure the ``parent``
is saved.
To get around this, we go through all our related fields &
call ``save`` on them if they have related, non-M2M data.
M2M data is handled by the ``ModelResource.save_m2m`` method.
"""
for field_name, field_object in self.fields.items():
if not getattr(field_object, 'is_related', False):
continue
if getattr(field_object, 'is_m2m', False):
continue
if not field_object.attribute:
continue
# Get the object.
# THIS HAS BEEN MOVED ABOVE the field_object.blank CHECK
try:
related_obj = getattr(bundle.obj, field_object.attribute)
except ObjectDoesNotExist:
related_obj = None
# THE 'not related_obj' CHECK HAS BEEN ADDED
if field_object.blank and not related_obj: # ADDED
continue
# Because sometimes it's ``None`` & that's OK.
if related_obj:
# THIS HAS BEEN ADDED
setattr(related_obj, field_object.related_name, bundle.obj) # ADDED
related_obj.save()
setattr(bundle.obj, field_object.attribute, related_obj)
それらを API に追加すると、すべてが機能するはずです (少なくとも 0.9.11 では)。修正の主な部分は、ToOneField に対して related_obj が適切に追加されなかったことです。私の RelatedToOneField サブクラスは、このチェックをフィールド ハイドレート コードに実装します。
編集:私はまた間違っていました。ToOneField は 0.9.12 ではまだ機能しません。私の落とし穴は、データベースに投稿しようとしていたのと同じデータを持つ UserProfileResource が既に存在していたことです。新しいものを作成する代わりに、その行を取得して変更しただけです。
これにも多くの時間を費やした後、バージョン 0.9.12 で修正された ToOneField のバグがあったようです (関連する議論については、Pablo の受け入れられた回答のコメントを参照してください)。
django-tastypie >= 0.9.12 の場合、次のように動作するはずです:
class UserResource(ModelResource):
profile = fields.ToOneField('path.to.api.UserProfileResource', 'profile', related_name='user', full=True)
class UserProfileResource(ModelResource):
home_address = fields.CharField(attribute='home_address')
user = fields.ToOneField(UserResource, attribute='user', related_name='profile')
django-tastypie <0.9.12 の場合は、次の手順を実行する必要があります。
class UserResource(ModelResource):
profile = fields.ToOneField('path.to.api.UserProfileResource', 'profile', related_name='user', full=True)
class UserProfileResource(ModelResource):
home_address = fields.CharField(attribute='home_address')
user = fields.ToManyField(UserResource, attribute='user', related_name='profile')
注: UserResource と UserProfileResource の順序を入れ替えたのは、私のメンタル モデルにとってより理にかなっているからです。