Tastypie で API を構築していますが、多対多フィールドを保存するときに問題が発生しました。
モデル call Pest と Call という別のモデルがあり、Call には、呼び出しpests
に適用できる害虫を表すというフィールドがあります。これらはすでに存在しており、ユーザーはその呼び出しに適用するものを 1 つ以上選択できます。Call オブジェクトと同時に作成する意図はありません。
デフォルトでは、POST 経由で新しい Call を作成しようとすると、次のエラーが表示されます。
{"error_message": "Cannot resolve keyword 'url' into field. Choices are: baitpoint, call, description, id, name, operator", "traceback": "Traceback (most recent call last):\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 217, in wrapper\n response = callback(request, *args, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 459, in dispatch_list\n return self.dispatch('list', request, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 491, in dispatch\n response = method(request, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 1357, in post_list\n updated_bundle = self.obj_create(bundle, **self.remove_api_resource_names(kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2150, in obj_create\n return self.save(bundle)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2300, in save\n m2m_bundle = self.hydrate_m2m(bundle)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 964, in hydrate_m2m\n bundle.data[field_name] = field_object.hydrate_m2m(bundle)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/fields.py\", line 853, in hydrate_m2m\n m2m_hydrated.append(self.build_related_resource(value, **kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/fields.py\", line 653, in build_related_resource\n return self.resource_from_uri(self.fk_resource, value, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/fields.py\", line 573, in resource_from_uri\n obj = fk_resource.get_via_uri(uri, request=request)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 838, in get_via_uri\n return self.obj_get(bundle=bundle, **self.remove_api_resource_names(kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2125, in obj_get\n object_list = self.get_object_list(bundle.request).filter(**kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/query.py\", line 655, in filter\n return self._filter_or_exclude(False, *args, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/query.py\", line 673, in _filter_or_exclude\n clone.query.add_q(Q(*args, **kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py\", line 1266, in add_q\n can_reuse=used_aliases, force_having=force_having)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py\", line 1134, in add_filter\n process_extras=process_extras)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py\", line 1332, in setup_joins\n \"Choices are: %s\" % (name, \", \".join(names)))\n\nFieldError: Cannot resolve keyword 'url' into field. Choices are: baitpoint, call, description, id, name, operator\n"}
それで、私は見て、この答えを見つけました。これは、同様の状況をカバーしているようです。hydrate_pests
次のように CallResource クラスにメソッドを追加しました。
class AbstractModelResource(ModelResource):
class Meta:
authorization = DjangoAuthorization()
authentication = ApiKeyAuthentication()
cache = SimpleCache(timeout=10)
always_return_data = True
class FilteredByOperatorAbstractModelResource(AbstractModelResource):
def authorized_read_list(self, object_list, bundle):
user = bundle.request.user
site_user = SiteUser.objects.get(user=user)
return object_list.filter(operator=site_user.operator)
class PestResource(FilteredByOperatorAbstractModelResource):
class Meta(AbstractModelResource.Meta):
queryset = Pest.objects.all()
resource_name = 'pest'
allowed_methods = ['get']
class CallResource(AbstractModelResource):
client = fields.ForeignKey(ClientResource, 'client')
operator = fields.ForeignKey(OperatorResource, 'operator')
pests = fields.ManyToManyField(PestResource, 'pests', null=True)
class Meta(AbstractModelResource.Meta):
queryset = Call.objects.all()
resource_name = 'call'
def hydrate_pests(self, bundle):
pests = bundle.data.get('pests', [])
pest_ids = []
for pest in pests:
m = re.search('\/api\/v1\/pests\/(\d+)\/', str(pest))
try:
id = m.group(1)
pest_ids.append(id)
except AttributeError:
pass
bundle.data['pests'] = Pest.objects.filter(id__in=pest_ids)
return bundle
フィールドは次のpests
ように渡されます。
0: "/api/v1/pests/6/"
1: "/api/v1/pests/7/"
また、実行時にペスト URL が正しく表示されbundle.data.get('pests', [])
ます。PDB を使用してトレースを設定すると、URL が通過されPest.objects.filter(id__in=pest_ids)
、正しいアイテムが返されることを確認できます。ただし、HTTP POST 要求は成功しますが、害虫フィールドは新しいデータを反映するように更新されていません。
誰かが私が間違っているところを見ることができますか? Pest オブジェクトのリストを bundle.data['pests'] に渡すのは正しいですか、それともこのデータをそのフィールドに渡す方法ではないですか?
実際に bundle.data に渡されるものは次のとおりです。
{'pests': [<Pest: Rats>, <Pest: Mice>], 'notes': u'Blah', 'first_choice_visit_time': u'2013-07-18T02:02', 'client': u'/api/v1/client/28/', 'date': u'2013-07-18', 'second_choice_visit_time': u'2014-03-03T03:02'}