2012-05-14 77 views
6

我想tastypie創建一個UserProfileResource,作爲我發佈到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命令:

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 

裏面涉及到試圖訪問用戶資源的ORM和而其創建related_objects UserProfileResource它不存在。哪個是對的。在創建related_objects之前,用戶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

感謝Pablo,對我來說也是2天!非常感激。 – johnoc

+1

其實我可能會說話太快。看起來它運行POST時確實有效。但是,當我再次嘗試時,我得到以下內容:'用戶'字段沒有數據,並且不允許空值。 – johnoc

+3

爲了澄清,我現在就開始工作了,但我不得不讓用戶提交一個「ToManyField」(正如Pablo在他的評論中所建議的那樣)。而且我還必須在配置文件字段中添加「null = True」。否則GET操作將不起作用。反正它現在好多了。感謝您的幫助巴勃羅! – johnoc

0

編輯#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 

然後覆蓋obj_create & save_related功能,在您的「頂」模式,或在這種情況下, 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子類將此檢查用於字段水合物代碼。

編輯:我又錯了,ToOneField的仍然不工作在0.9.12。我的問題是已經有一個UserProfileResource與我試圖在數據庫中發佈的相同數據。它只是抓住那一行並修改它,而不是創建新的東西。


也花費了太多的時間在這之後,似乎有對ToOneField的一個bug(見巴勃羅對相關討論接受的答案評論),其固定在0.9.12版本。

如果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對我的心智模型更有意義。

相關問題