Django Tastypie ToManyField в родительском ресурсе, кажется, ломает POST для ресурса chile

Я использую Django 1.4.3 и TastyPie 0.9.11.

У меня есть следующие две модели django:

class Event(models.Model):
    organizer = models.ForeignKey(User, related_name='Organizador')
    store = models.ForeignKey(Store, related_name='Tienda')
    name = models.CharField('Titulo', max_length=50)
    event_time = models.DateTimeField('Fecha y Hora del Evento')
    creation_date = models.DateTimeField('Fecha de Creación', auto_now_add=True)
    requires_confirmation = models.BooleanField('Require Confirmación')

    class Meta:
        verbose_name = "Encuentro"
        verbose_name_plural = "Encuentros"


class EventInvitees(models.Model):
    event = models.ForeignKey(Event, related_name='invitees')
    invitee = models.ForeignKey(User, verbose_name='Invitado')
    confirmed = models.BooleanField('Confirmado')
    confirmation_date = models.DateTimeField('Fecha de Confirmación', null=True, auto_now=True)

    class Meta:
        verbose_name = "Invitados"
        verbose_name_plural = "Invitados"

Затем у меня есть следующие ресурсы API:

class EventInviteesResource(ModelResource):
    user = fields.ForeignKey(UserResource, 'invitee', full=True)
    event = fields.ForeignKey('bbservices.api.EventResource', 'event')

    class Meta:
        queryset = EventInvitees.objects.all()
        authentication = ApiKeyAuthentication()
        authorization = Authorization()


class EventResource(ModelResource):
    invitees = fields.ToManyField('bbservices.api.EventInviteesResource', 'invitees', full=True)
    store = fields.ForeignKey(StoreResource, 'store', full=True)

    class Meta:
        #default_format = 'application/json'

        queryset = Event.objects.all()

        fields = ['organizer_id', 'store', 'name', 'event_time', 'requires_confirmation']
        include_resource_uri = False
        #list_allowed_methods = ['get', 'post']
        authentication = ApiKeyAuthentication()
        authorization = Authorization()
        filtering = {
            #'user': ALL_WITH_RELATIONS
            'event_time': ['exact', 'range', 'gt', 'gte', 'lt', 'lte'],
        }

    def dehydrate_event_time(self, bundle):
        return bundle.data['event_time']

    def obj_create(self, bundle, request=None, **kwargs):
        return super(EventResource, self).obj_create(bundle, request, organizer_id=request.user.pk, store_id=bundle.data['store_id'])

Как видите, я настроил отношение "ToManyField", чтобы приглашенные на события отображались в списках GET ресурса Events. Это работает правильно. Обратите внимание, что существует также отношение FK к «магазину», и это также работает.

Ошибка возникает при отправке следующего сообщения в «EventInviteesResource» для создания и EventInvitee:

POST http://XXXX:8000/api/v1/eventinvitees/?username=user&api_key=XXXXXXX

{
  "event" : {"pk" : 30},
  "invitee" : 2,
  "confirmed" : true
}

Возвращаемая ошибка:

«Поле «приглашенные» не содержит данных и не может содержать нулевое значение».

Обратите внимание, что «приглашенные» НЕ существуют в «EventInviteesResource», а вместо этого находятся в «родительском» ресурсе «EventResource». Поэтому я не понимаю, как это может быть ошибкой. Если я закомментирую эту строку:

invitees = fields.ToManyField('bbservices.api.EventInviteesResource', 'invitees', full=True)

ресурс «EventResource» ошибка «приглашенные» исчезает, и ошибка становится:

{"error_message": "", "traceback": "Traceback (most recent call last):\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 192, in wrapper\n response = callback(request, *args, **kwargs)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 397, in dispatch_list\n return self.dispatch('list', request, **kwargs)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 427, in dispatch\n response = method(request, **kwargs)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 1165, in post_list\n updated_bundle = self.obj_create(bundle, request=request, **self.remove_api_resource_names(kwargs))\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 1774, in obj_create\n bundle = self.full_hydrate(bundle)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 698, in full_hydrate\n value = field_object.hydrate(bundle)\n\n File \"/Library/Python/2.7/site-packages/tastypie/fields.py\", line 636, in hydrate\n value = super(ToOneField, self).hydrate(bundle)\n\n File \"/Library/Python/2.7/site-packages/tastypie/fields.py\", line 154, in hydrate\n elif self.attribute and getattr(bundle.obj, self.attribute, None):\n\n File \"/Library/Python/2.7/site-packages/django/db/models/fields/related.py\", line 343, in __get__\n raise self.field.rel.to.DoesNotExist\n\nDoesNotExist\n"}

Если я попытаюсь опубликовать это:

{
    "store_id" : 1, 
    "name" : "With Invitees", 
    "event_time" : "2013-02-06T18:30-3:00",
    "requires_confirmation" : true,
    "invitees" : [
                    {
                      "invitee": {"pk" : 1}
                    }
                ]
}

к ресурсу EventResource с неповрежденной связью «приглашенные», ошибка:

{"error_message": "int() argument must be a string or a number, not 'dict'", "traceback": "Traceback (most recent call last):\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 192, in wrapper\n response = callback(request, *args, **kwargs)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 397, in dispatch_list\n return self.dispatch('list', request, **kwargs)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 427, in dispatch\n response = method(request, **kwargs)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 1165, in post_list\n updated_bundle = self.obj_create(bundle, request=request, **self.remove_api_resource_names(kwargs))\n\n File \"/Users/jleidigh/Documents/Starbucks - In Office/trunk/backend/bbservices/api.py\", line 234, in obj_create\n return super(EventResource, self).obj_create(bundle, request, organizer_id=request.user.pk, store_id=bundle.data['store_id'])\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 1783, in obj_create\n m2m_bundle = self.hydrate_m2m(bundle)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 743, in hydrate_m2m\n bundle.data[field_name] = field_object.hydrate_m2m(bundle)\n\n File \"/Library/Python/2.7/site-packages/tastypie/fields.py\", line 742, in hydrate_m2m\n m2m_hydrated.append(self.build_related_resource(value, **kwargs))\n\n File \"/Library/Python/2.7/site-packages/tastypie/fields.py\", line 593, in build_related_resource\n return self.resource_from_data(self.fk_resource, value, **kwargs)\n\n File \"/Library/Python/2.7/site-packages/tastypie/fields.py\", line 548, in resource_from_data\n return fk_resource.obj_update(fk_bundle, **data)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 1814, in obj_update\n bundle.obj = self.obj_get(request, **lookup_kwargs)\n\n File \"/Library/Python/2.7/site-packages/tastypie/resources.py\", line 1752, in obj_get\n base_object_list = self.get_object_list(request).filter(**kwargs)\n\n File \"/Library/Python/2.7/site-packages/django/db/models/query.py\", line 624, in filter\n return self._filter_or_exclude(False, *args, **kwargs)\n\n File \"/Library/Python/2.7/site-packages/django/db/models/query.py\", line 642, in _filter_or_exclude\n clone.query.add_q(Q(*args, **kwargs))\n\n File \"/Library/Python/2.7/site-packages/django/db/models/sql/query.py\", line 1250, in add_q\n can_reuse=used_aliases, force_having=force_having)\n\n File \"/Library/Python/2.7/site-packages/django/db/models/sql/query.py\", line 1185, in add_filter\n connector)\n\n File \"/Library/Python/2.7/site-packages/django/db/models/sql/where.py\", line 69, in add\n value = obj.prepare(lookup_type, value)\n\n File \"/Library/Python/2.7/site-packages/django/db/models/sql/where.py\", line 320, in prepare\n return self.field.get_prep_lookup(lookup_type, value)\n\n File \"/Library/Python/2.7/site-packages/django/db/models/fields/related.py\", line 137, in get_prep_lookup\n return self._pk_trace(value, 'get_prep_lookup', lookup_type)\n\n File \"/Library/Python/2.7/site-packages/django/db/models/fields/related.py\", line 210, in _pk_trace\n v = getattr(field, prep_func)(lookup_type, v, **kwargs)\n\n File \"/Library/Python/2.7/site-packages/django/db/models/fields/__init__.py\", line 310, in get_prep_lookup\n return self.get_prep_value(value)\n\n File \"/Library/Python/2.7/site-packages/django/db/models/fields/__init__.py\", line 537, in get_prep_value\n return int(value)\n\nTypeError: int() argument must be a string or a number, not 'dict'\n"}

Я считаю, что эта ошибка задокументирована здесь:

https://github.com/toastdriven/django-tastypie/issues/307

Если я прокомментирую строку «приглашенные» и отправлю POST в EventResource, ошибка исчезнет, ​​но, конечно, приглашенные не будут созданы.

Итак........ у кого-нибудь есть идеи? Это просто еще одна ошибка, связанная с проблемой 307 (ссылка выше), или я что-то не так делаю??

Большое спасибо заранее!!!!


person Jason Leidigh    schedule 18.01.2013    source источник


Ответы (2)


Хорошо, я нашел свой собственный ответ. В EventInviteesResource

user = fields.ForeignKey(UserResource, 'invitee', full=True)

Должно быть следующее, чтобы отразить мою модель Django:

invitee = fields.ForeignKey(UserResource, 'invitee', full=True)

Хотя это кажется логичным, я должен сказать, что ошибка «приглашенных» (обратите внимание на «s»), которую я получал, НЕ работает, ну да ладно...

Дополнительный ответ, в EventResource обновите:

invitees = fields.ToManyField('bbservices.api.EventInviteesResource', 'invitees', full=True)

To:

invitees = fields.ToManyField('bbservices.api.EventInviteesResource', 'invitees', related_name='event', full=True)

Теперь вы можете публиковать сообщения в EventResource с приглашенными, а также создавать их автоматически. Данные поста будут выглядеть так:

{
    "store" : "/api/v1/store/2/", 
    "name" : "Yes again?", 
    "event_time" : "2013-02-06T18:30-3:00",
    "requires_confirmation" : true,
    "invitees" : [
                    {
                      "invitee" : "/api/v1/user/1/",
                      "confirmed" : true
                    }
                ]
}

Итак, теперь мое единственное сомнение в том, что... может ли кто-нибудь сказать мне, почему я не могу использовать синтаксис PK для FK:

{ "store" : {"pk" : 2}, ...

Это приводит к ошибкам, указывающим, что поля для объекта Store не могут быть нулевыми, как если бы это была попытка создать новый объект Store. Если я использую путь URI, как показано ниже, он работает нормально:

{ "store" : "/api/v1/store/2/", ...

Но я бы предпочел не возвращать полный URI, и мне придется это сделать. Вот почему я использую трюк store_id в obj_create, но он ОЧЕНЬ неудобен...

Любые идеи?

person Jason Leidigh    schedule 18.01.2013
comment
Я наблюдал ту же проблему на своей стороне. Это было немного странно, поскольку простое добавление дочернего ресурса к родительскому очищало только некоторые несвязанные внешние отношения m2m. Немного подумав, я обнаружил, что затронутое отношение m2m использует пользовательскую сквозную таблицу для хранения дополнительных данных с каждым отношением. Я предполагаю, что при использовании синтаксиса {pk:xx}, deliciouspie неправильно обрабатывает сквозную таблицу и просто очищает это поле m2m. При использовании полного формата URI проблема исчезает. - person BastiBen; 03.10.2013

Когда вы предоставляете {"store":"/api/v1/store/2/"}, вы указываете только значение хранилища EventResource. Но когда вы предоставляете {"store": {"pk":2}}, вы не только указываете значение store, но и редактируете store (сохраняете связанный объект). Это вкусный "стиль".

Что мы можем сделать, так это создать набор функций Javascript, которые автоматически преобразуют из/в uri ресурса. Использование существующих фреймворков Javascript MVC, таких как AgularJS или Backbone, в качестве внешнего интерфейса и Django Tastypie в качестве внутреннего интерфейса, очень эффективно.

person adityasdarma1    schedule 21.12.2013