【问题标题】:Creating related resources with Tastypie使用 Tastypie 创建相关资源
【发布时间】:2012-05-14 11:30:19
【问题描述】:

由于我 POST 到 UserResource,我希望tastepie 创建一个 UserProfileResource。

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。显然我正在做一些非常愚蠢的事情。

那里的任何人都可以发光吗? 非常感谢 约翰克

我按照 Pablo 下面的建议修改了我的代码。

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。

其他人看到了吗??

【问题讨论】:

  • 我这里也有同样的问题。

标签: django tastypie


【解决方案1】:

两天后我终于设法保存了相关资源,问题是您必须指定关系的双方及其相关名称,在您的情况下,它会是这样的:

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)

【讨论】:

  • 感谢 Pablo,我也已经 2 天了!非常感谢。
  • 其实我可能说得太早了。当我运行 POST 时,它看起来确实有效。但是当我再次尝试时,我得到以下信息:“用户”字段没有数据并且不允许空值。
  • 澄清一下,我现在可以使用它,但我必须让用户提交一个“ToManyField”(正如 Pablo 在他的评论中建议的那样)。而且我还必须在配置文件字段中添加“null=True”。否则 GET 操作将不起作用。反正现在一切都好了。感谢您对巴勃罗的所有帮助!
  • 你是怎么想到这个的?为什么会有什么不同?即使它的模型没有对应关系,您是否在您的对立资源上定义了一个 RelatedField?我没有对应关系,你的建议似乎对我没有任何作用。我觉得根本问题是别的。
【解决方案2】:

编辑 #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 上)。修复的主要部分是没有为 ToOneField 正确添加相关 obj。我的 RelatedToOneField 子类实现了对字段水合物代码的这种检查。

编辑:我又错了,ToOneField 在 0.9.12 中仍然不起作用。我的问题是已经有一个 UserProfileResource 与我试图在数据库中发布的相同数据。它只是抓取了该行并对其进行了修改,而不是创建新内容。


在这方面也花费了太多时间之后,似乎 ToOneField 存在一个错误,该错误已在 0.9.12 版中修复(有关相关讨论,请参阅 Pablo 接受的答案中的 cmets)。

如果 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

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 的顺序,因为这对我的心智模型更有意义。

【讨论】:

    猜你喜欢
    • 2019-06-16
    • 1970-01-01
    • 2012-07-30
    • 2013-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-30
    • 1970-01-01
    相关资源
    最近更新 更多