2012-05-14 2 views
6

Я хотел бы, чтобы tastypie создавал UserProfileResource в результате того, что я отправлял POST на UserResource.Создание связанных ресурсов с Tastypie

models.py:

class UserProfile(models.Model): 
    home_address = models.TextField() 
    user = models.ForeignKey(User, unique=True) 

resources.py

class UserProfileResource(ModelResource): 
    home_address = fields.CharField(attribute='home_address') 

    class Meta: 
     queryset = UserProfile.objects.all() 
     resource_name = 'profile' 
     excludes = ['id'] 
     include_resource_uri = False 


class UserResource(ModelResource): 
    profile = fields.ToOneField(UserProfileResource, 'profile', full=True) 
    class Meta: 
     queryset = User.objects.all() 
     resource_name = 'user' 
     allowed_methods = ['get', 'post', 'delete', 'put'] 
     fields = ['username'] 
     filtering = { 
       'username': ALL, 
       } 

локон команда:

curl -v -H "Content-Type: application/json" -X POST --data '{"username":"me", "password":"blahblah", "profile":{"home_address":"somewhere"}}' http://127.0.0.1:8000/api/user/ 

Но я получаю:

Django Version: 1.4 
Exception Type: IntegrityError 
Exception Value: 
null value in column "user_id" violates not-null constraint 

Это похоже на сценарий с курицей и яйцом. Мне нужен user_id для создания UserProfileResource, и мне нужен профиль для создания UserResource. Очевидно, я делаю что-то очень глупое.

Может ли кто-нибудь там светить светом? Большое спасибо johnoc

Я изменил мой код, как Пабло предложил ниже.

class UserProfileResource(StssRessource): 
    home_address = fields.CharField(attribute='home_address') 
    user = fields.ToOneField('resources.UserResource', attribute='user', related_name='profile') 

    class Meta: 
     queryset = UserProfile.objects.all() 
     resource_name = 'profile' 


class UserResource(ModelResource): 
    profile = fields.ToOneField('resources.UserProfileResource', attribute='profile', related_name = 'user', full=True) 
    class Meta: 
     queryset = User.objects.all() 
     resource_name = 'user' 

Но я получаю:

Django Version: 1.4 
Exception Type: DoesNotExist 

Что касается пытается получить доступ к ресурсу пользователя в ОРМ и не существует, а его создание related_objects UserProfileResource. Что правильно. Пользовательский ORM не создается до тех пор, пока не будут созданы связанные объекты.

Кто-нибудь еще видел это ??

+0

У меня такая же проблема здесь. – Pablo

ответ

15

Через 2 дня я наконец-то удалось сохранить соответствующие ресурсы, проблема заключалась в том, что вы должны указать обе стороны отношения и связанные с ними имена, в вашем случае это будет что-то вроде этого:

class UserProfileResource(ModelResource): 
    home_address = fields.CharField(attribute='home_address') 
    user = fields.ToOneField('path.to.api.UserResource', attribute='user', related_name='profile') 
     #in my case it was a toManyField, I don't know if toOneField works here, you can try toManyField. 

class UserResource(ModelResource): 
    profile = fields.ToOneField(UserProfileResource, 'profile', related_name='user', full=True) 
+1

Спасибо, Пабло, для меня тоже было 2 дня! Очень признателен. – johnoc

+1

На самом деле, возможно, я говорил слишком рано. Похоже, он работал, когда я запускал POST. Но когда я попытался снова, я получаю следующее: поле «пользователь» не имеет данных и не допускает нулевого значения. – johnoc

+3

Чтобы уточнить, у меня есть это сейчас, но я должен был заставить пользователя подавать «ToManyField» (как предложил Пабло в своем комментарии). И мне также пришлось добавить «null = True» в поле профиля. В противном случае операции GET не будут работать. В любом случае, теперь все лучше. Спасибо за вашу помощь Пабло! – johnoc

0

EDIT # 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 

Затем отменяют save_related функции obj_create & в модели «сверху», или в данном случае, UserResource. Вот соответствующие переопределяет:

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). Основная часть исправления связана с тем, что related_obj не были добавлены должным образом для ToOneField. Подкласс relatedToOneField реализует эту проверку в полевого гидратного кода.

EDIT: Я был не в порядке, ToOneField по-прежнему не работает в 0.9.12. Я понял, что уже есть UserProfileResource с теми же данными, которые я пытался опубликовать в базе данных.Он просто схватил эту строку и изменил ее, а не создал что-то новое.


После также тратить слишком много времени на это, кажется, что там была ошибка для ToOneField, которая была исправлена ​​в версии 0.9.12 (см комментариев в принятом ответе Паб для соответствующего обсуждения).

Если Джанго-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') 

если Джанго-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, поскольку это имело больше смысла для моей ментальной модели.