2012-05-14 49 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对我的心智模型更有意义。

相关问题