2013-05-29 30 views
53

Trong ứng dụng của tôi, tôi có các mô hình sau:Làm cách nào để áp dụng bộ lọc cho tài nguyên lồng nhau trong khung công tác REST của Django?

class Zone(models.Model): 
    name = models.SlugField() 

class ZonePermission(models.Model): 
    zone = models.ForeignKey('Zone') 
    user = models.ForeignKey(User) 
    is_administrator = models.BooleanField() 
    is_active = models.BooleanField() 

Tôi đang sử dụng khuôn khổ Django REST để tạo ra một nguồn lực mà trả về chi tiết khu vực cộng với một tài nguyên lồng nhau thể hiện quyền của người sử dụng chứng thực cho vùng đó. Sản lượng nên được một cái gì đó như thế này:

{ 
    "name": "test", 
    "current_user_zone_permission": { 
     "is_administrator": true, 
     "is_active": true 
    } 
} 

Tôi đã tạo serializers như vậy:

class ZonePermissionSerializer(serializers.ModelSerializer): 
    class Meta: 
     model = ZonePermission 
     fields = ('is_administrator', 'is_active') 

class ZoneSerializer(serializers.HyperlinkedModelSerializer): 
    current_user_zone_permission = ZonePermissionSerializer(source='zonepermission_set') 

    class Meta: 
     model = Zone 
     fields = ('name', 'current_user_zone_permission') 

Vấn đề với điều này là khi tôi yêu cầu một khu vực đặc biệt, tài nguyên lồng nhau trả về hồ sơ ZonePermission cho tất cả người dùng có quyền đối với khu vực đó. Có cách nào áp dụng bộ lọc trên request.user vào tài nguyên lồng nhau không?

BTW Tôi không muốn sử dụng số HyperlinkedIdentityField cho điều này (để giảm thiểu yêu cầu http).

Giải pháp

Đây là giải pháp tôi đã triển khai dựa trên câu trả lời bên dưới. Tôi đã thêm mã sau vào lớp serializer của tôi:

current_user_zone_permission = serializers.SerializerMethodField('get_user_zone_permission') 

def get_user_zone_permission(self, obj): 
    user = self.context['request'].user 
    zone_permission = ZonePermission.objects.get(zone=obj, user=user) 
    serializer = ZonePermissionSerializer(zone_permission) 
    return serializer.data 

Cảm ơn rất nhiều vì giải pháp!

Trả lời

27

Tôi phải đối mặt với cùng một tình huống. Giải pháp tốt nhất mà tôi đã tìm thấy là sử dụng một số SerializerMethodField và có truy vấn phương thức đó và trả về các giá trị mong muốn. Bạn có thể truy cập vào request.user theo phương thức đó thông qua self.context['request'].user.

Tuy nhiên, điều này có vẻ hơi khó khăn. Tôi khá mới đối với DRF, vì vậy có thể ai đó có nhiều kinh nghiệm hơn có thể tham gia.

+0

Cảm ơn đề nghị của bạn. Có thể một 'SerializerMethodField' trả về một cấu trúc hay chỉ là một trường phẳng? –

+0

Nó có thể trả về một cấu trúc. – user2437225

+0

Tôi sẽ có cách tiếp cận này - cảm ơn. Nếu không có đề xuất 'chính thức' nào khác đến thì tôi sẽ chấp nhận điều này làm câu trả lời. –

6

Bạn phải sử dụng bộ lọc thay vì nhận được, nếu không, nhiều lần quay trở lại bạn sẽ nhận được Ngoại lệ.

current_user_zone_permission = serializers.SerializerMethodField('get_user_zone_permission') 

def get_user_zone_permission(self, obj): 
    user = self.context['request'].user 
    zone_permission = ZonePermission.objects.filter(zone=obj, user=user) 
    serializer = ZonePermissionSerializer(zone_permission,many=True) 
    return serializer.data 
5

Bây giờ bạn có thể phân lớp các ListSerializer, sử dụng phương pháp tôi đã mô tả ở đây: https://stackoverflow.com/a/28354281/3246023

Bạn có thể phân lớp các ListSerializer và ghi đè lên các phương pháp to_representation.

Theo mặc định, phương thức to_representation gọi data.all() trên bộ truy vấn lồng nhau. Vì vậy, bạn có hiệu quả cần phải thực hiện dữ liệu = data.filter (** your_filters) trước khi phương thức được gọi. Sau đó, bạn cần phải thêm ListSerializer được phân lớp của bạn dưới dạng list_serializer_class vào meta của serializer lồng nhau.

  1. lớp con ListSerializer, ghi đè to_representation và sau đó gọi siêu
  2. add subclassed ListSerializer như list_serializer_class meta trên lồng nhau Serializer
+0

Tôi cũng thích phương pháp này tốt hơn, vì nó vẫn cho phép giữ trường có thể ghi nếu bạn cần. – jgiralt

4

Nếu bạn đang sử dụng QuerySet/lọc ở nhiều nơi, bạn có thể sử dụng chức năng getter trên mô hình của bạn, và sau đó thả 'kwarg' nguồn cho Serializer/Field.DRF tự động gọi các chức năng/cuộc gọi nếu tìm thấy chúng khi sử dụng chức năng get_attribute.

class Zone(models.Model): 
    name = models.SlugField() 

    def current_user_zone_permission(self): 
     return ZonePermission.objects.get(zone=self, user=user) 

Tôi thích phương pháp này vì nó giữ cho API của bạn nhất quán dưới mui xe với api qua HTTP.

class ZoneSerializer(serializers.HyperlinkedModelSerializer): 
    current_user_zone_permission = ZonePermissionSerializer() 

    class Meta: 
     model = Zone 
     fields = ('name', 'current_user_zone_permission') 

Hy vọng điều này sẽ giúp một số người!

Lưu ý: Tên không cần để khớp, bạn vẫn có thể sử dụng nguồn kwarg nếu cần/muốn.

Chỉnh sửa: Tôi vừa nhận ra rằng chức năng trên mô hình không có quyền truy cập vào người dùng hoặc yêu cầu. Vì vậy, có lẽ một trường mô hình tùy chỉnh/ListSerializer sẽ phù hợp hơn với nhiệm vụ này.

2

Tôi sẽ thực hiện theo một trong hai cách.

1) Hoặc làm điều đó thông qua prefetch theo quan điểm của bạn:

serializer = ZoneSerializer(Zone.objects.prefetch_related(
     Prefetch('zone_permission_set', 
      queryset=ZonePermission.objects.filter(user=request.user), 
      to_attr='current_user_zone_permission')) 
     .get(id=pk)) 

2) Hoặc làm điều đó dù .to_representation:

class ZoneSerializer(serializers.HyperlinkedModelSerializer): 

    class Meta: 
     model = Zone 
     fields = ('name',) 

    def to_representation(self, obj): 
     data = super(ZoneSerializer, self).to_representation(obj) 
     data['current_user_zone_permission'] = ZonePermissionSerializer(ZonePermission.objects.filter(zone=obj, user=self.context['request'].user)).data 
     return data 
Các vấn đề liên quan