2012-05-14 46 views
6

Tôi muốn người yêu thích tạo một UserProfileResource do tôi đăng lên một UserResource.Tạo các tài nguyên có liên quan với 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, 
       } 

lệnh 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/ 

Nhưng tôi nhận được:

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

Nó có vẻ giống như một kịch bản gà và trứng. Tôi cần user_id để tạo UserProfileResource và tôi cần cấu hình để tạo UserResource. Rõ ràng tôi đang làm điều gì đó rất ngớ ngẩn.

Có ai ở ngoài đó tỏa sáng không? Rất cám ơn johnoc

tôi sửa đổi mã của tôi như Pablo gợi ý dưới đây.

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' 

Nhưng đang nhận được:

Django Version: 1.4 
Exception Type: DoesNotExist 

nào liên quan đến cố gắng để truy cập vào tài nguyên người dùng trong ORM và nó không tồn tại trong khi nó tạo ra related_objects UserProfileResource. Đó là chính xác. User ORM isnt được tạo cho đến sau khi các related_object được tạo ra.

Bất cứ ai khác nhìn thấy điều này ??

+0

Tôi gặp vấn đề tương tự ở đây. – Pablo

Trả lời

15

Sau 2 ngày cuối cùng tôi quản lý để tiết kiệm tài nguyên liên quan, vấn đề là bạn phải xác định cả hai mặt của mối quan hệ và tên liên quan của họ, trong trường hợp của bạn nó sẽ là một cái gì đó như thế:

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

Cảm ơn Pablo, đã 2 ngày rồi! Nhiều đánh giá cao. – johnoc

+1

Thực ra tôi có thể đã nói quá sớm. Có vẻ như nó đã hoạt động khi tôi chạy POST. Nhưng khi tôi cố gắng một lần nữa tôi nhận được những điều sau đây: Trường 'người dùng' không có dữ liệu và không cho phép một giá trị null. – johnoc

+3

Để làm rõ, tôi có nó bây giờ làm việc nhưng tôi đã phải làm cho người dùng nộp một "ToManyField" (như Pablo gợi ý trong bình luận của mình). Và tôi cũng phải thêm "null = True" vào trường profile. Nếu không, các hoạt động GET sẽ không hoạt động. Dù sao thì tốt hơn hết. Cảm ơn tất cả sự giúp đỡ của bạn Pablo! – johnoc

0

EDIT # 2: Cuối cùng đã tìm ra cách để sửa chữa mọi thứ, nhưng tiếc là nó đòi hỏi một chút subclassing và ghi đè. Đây là cách tôi đã nhận nó làm việc:

Đầu tiên, tạo một lớp con lĩnh vực mới - Tôi gọi RelatedToOneField tôi:

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 

Sau đó ghi đè lên obj_create & chức năng save_related trong mô hình "top" của bạn, hoặc trong trường hợp này, UserResource. Dưới đây là các ghi đè có liên quan:

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) 

Sau khi bạn thêm chúng vào API, mọi thứ sẽ hoạt động (Ít nhất là trên 0.9.11). Phần chính của bản sửa lỗi này là của related_obj không được thêm vào đúng cho ToOneField. Lớp con RelatedToOneField của tôi thực hiện kiểm tra này vào mã hydrate của trường.

EDIT: Tôi đã sai lần nữa, ToOneField vẫn không hoạt động trong 0.9.12. Gotcha của tôi là đã có một UserProfileResource với cùng một dữ liệu mà tôi đã cố gắng đăng trong cơ sở dữ liệu.Nó chỉ nắm lấy hàng đó và sửa đổi nó thay vì tạo ra một cái gì đó mới.


Sau cũng dành quá nhiều thời gian về vấn đề này, có vẻ như rằng có một lỗi cho ToOneField rằng đã được cố định trong phiên bản 0.9.12 (xem chú thích trong câu trả lời chấp nhận của Pablo để thảo luận có liên quan).

Nếu django-tastypie> = 0.9.12, sau đây nên làm việc:

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') 

nếu django-tastypie < 0.9.12, bạn sẽ cần phải làm như sau:

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') 

Lưu ý: đã chuyển thứ tự của UserResource & UserProfileResource vì điều đó có ý nghĩa hơn đối với mô hình tinh thần của tôi.

Các vấn đề liên quan