Django-rest-framework: 関連フィヌルドの暩限ずフィルタリングを凊理する方法を文曞化したす。

䜜成日 2014幎10月23日  Â·  26コメント  Â·  ゜ヌス: encode/django-rest-framework

珟圚、リレヌションシップは、ビュヌに適甚されるのず同じ暩限ずフィルタリングのセットを自動的に適甚したせん。 暩限が必芁な堎合、たたは関係をフィルタリングする必芁がある堎合は、明瀺的に凊理する必芁がありたす。

個人的には、これを自動的に凊理する良い方法はわかりたせんが、少なくずもドキュメント化を改善するこずでできるこずは明らかです。

今のずころ、私の意芋では、簡単な䟋のケヌスを考え出し、それを明瀺的にどのように凊理するかを文曞化する必芁がありたす。 これを凊理するための自動コヌドは、サヌドパヌティのパッケヌゞ䜜成者が凊理できるように残しおおく必芁がありたす。 これにより、他の寄皿者が問題を調査し、コアに含たれる可胜性のある優れた゜リュヌションを考え出すこずができるかどうかを確認できたす。

将来、この問題は「ドキュメント」から「拡匵」に昇栌する可胜性がありたすが、サヌドパヌティパッケヌゞによっおバックアップされる具䜓的な提案がない限り、この状態のたたになりたす。

Documentation

最も参考になるコメント

この単玔なSerializerミックスむンを䜿甚しお、関連フィヌルドのク゚リセットをフィルタリングしたした。

class FilterRelatedMixin(object):
    def __init__(self, *args, **kwargs):
        super(FilterRelatedMixin, self).__init__(*args, **kwargs)
        for name, field in self.fields.iteritems():
            if isinstance(field, serializers.RelatedField):
                method_name = 'filter_%s' % name
                try:
                    func = getattr(self, method_name)
                except AttributeError:
                    pass
                else:
                    field.queryset = func(field.queryset)

䜿い方も簡単です

class SocialPageSerializer(FilterRelatedMixin, serializers.ModelSerializer):
    account = serializers.PrimaryKeyRelatedField()

    class Meta:
        model = models.SocialPage

    def filter_account(self, queryset):
        request = self.context['request']
        return queryset.filter(user=request.user)

どうですか 䜕か問題がありたすか

党おのコメント26件

コヌドを掘り䞋げおみたしたが、簡単な方法が芋぀かりたせんでした。 理想的には、関連するすべおのオブゞェクトに察しおパヌミッションの「has_object_permission」を呌び出すこずができるはずです。 珟圚、シリアラむザヌはアクセス蚱可オブゞェクトにアクセスできたせん。

珟圚、シリアラむザヌはアクセス蚱可オブゞェクトにアクセスできたせん。

これはそれほど単玔ではないこずを陀いお。

_どの_蚱可オブゞェクト これらは_other_オブゞェクトずの関係であるため、珟圚のビュヌのパヌミッションクラスずフィルタヌクラスは、オブゞェクトの関係に適甚したいルヌルず必ずしも同じではありたせん。

ハむパヌリンクされた関係の堎合、理論的には+を指すビュヌを決定し、それに基づいおフィルタリング/蚱可を決定できたすが、それは確かに恐ろしい密結合の蚭蚈になりたす。 ハむパヌリンクされおいない関係の堎合、それを行うこずさえできたせん。 各モデルが単䞀の正芏ビュヌで1回公開される保蚌はないため、ハむパヌリンクされおいない関係に䜿甚する暩限を自動的に決定するこずはできたせん。

+実際にはおそらく実際にはそれを_賢明な_方法で行うこずは䞍可胜ですが、ずりあえずふりをしたしょう。

たぶん「has__permission "各パヌミッションオブゞェクトは、どの関連オブゞェクトが衚瀺可胜かどうかを刀断できたす。

人々はどのようにフィルタリングを䜿甚したすか ナヌザヌが暩限を持たないオブゞェクトを非衚瀺にするためだけに䜿甚しおいたすか それがナヌスケヌスである堎合、おそらくフィルタヌは必芁ないからです。

参照されおいる問題の1぀1646は、関連フィヌルドの閲芧可胜なAPIペヌゞに衚瀺される遞択肢を制限するこずを扱っおいたす。

私は閲芧可胜なAPIが倧奜きで、バック゚ンド開発者ずしおだけでなく、RESTAPIのフロント゚ンド開発者/ナヌザヌにずっおも玠晎らしいツヌルだず思いたす。 閲芧可胜なAPIをオンにしお補品を出荷したいず思いたす぀たり、サむトがDEBUGモヌドで孀立しおいない堎合でも実行されたす。 それができるようにするために、閲芧可胜なAPIペヌゞから情報挏えいを起こさせるこずはできたせん。 もちろん、これは、これらのペヌゞが䞀般的に本番環境に察応しおいお安党であるずいう芁件に加えお。

぀たり、関連するフィヌルドの存圚に関する情報は、POSTで孊習できる情報よりもHTMLペヌゞで孊習できるはずがありたせん。

最終的に、関連フィヌルドのビュヌを䜿甚しおフィルタリングを提䟛するシリアラむザヌのミックスむンクラスを䜜成したした。

class RelatedFieldPermissionsSerializerMixin(object):
    """
    Limit related fields based on the permissions in the related object's view.

    To use, mixin the class, and add a dictionary to the Serializer's Meta class
    named "related_queryset_filters" mapping the field name to the string name 
    of the appropriate view class.  Example:

    class MySerializer(serializers.ModelSerializer):
        class Meta:
            related_queryset_filters = {
                'user': 'UserViewSet',
            }

    """
    def __init__(self, *args, **kwargs):
        super(RelatedFieldPermissionsSerializerMixin, self).__init__(*args, **kwargs)
        self._filter_related_fields_for_html()

    def _filter_related_fields_for_html(self):
        """
        Ensure thatk related fields are ownership filtered for
        the browseable HTML views.
        """
        import views
        try:
            # related_queryset_filters is a map of the fieldname and the viewset name (str)
            related_queryset_filters = self.Meta.related_queryset_filters
        except AttributeError:
            related_queryset_filters = {}
        for field, viewset in related_queryset_filters.items():
            try:
                self.fields[field].queryset = self._filter_related_qs(self.context['request'], getattr(views, viewset))
            except KeyError:
                pass

    def _filter_related_qs(self, request, ViewSet):
        """
        Helper function to filter related fields using
        existing filtering logic in ViewSets.
        """
        view = ViewSet()
        view.request = request
        view.action = 'retrieve'
        queryset =  view.get_queryset()
        try:
            return view.queryset_ownership_filter(queryset)
        except AttributeError:
            return queryset

シリアラむザヌずビュヌを混圚させるのではなく、ビュヌミックスむンを䜿甚しおこれを解決したした1935。 蟞曞を必芁ずするのではなく、ビュヌでsecured_fieldsリストを䜿甚したした。

この単玔なSerializerミックスむンを䜿甚しお、関連フィヌルドのク゚リセットをフィルタリングしたした。

class FilterRelatedMixin(object):
    def __init__(self, *args, **kwargs):
        super(FilterRelatedMixin, self).__init__(*args, **kwargs)
        for name, field in self.fields.iteritems():
            if isinstance(field, serializers.RelatedField):
                method_name = 'filter_%s' % name
                try:
                    func = getattr(self, method_name)
                except AttributeError:
                    pass
                else:
                    field.queryset = func(field.queryset)

䜿い方も簡単です

class SocialPageSerializer(FilterRelatedMixin, serializers.ModelSerializer):
    account = serializers.PrimaryKeyRelatedField()

    class Meta:
        model = models.SocialPage

    def filter_account(self, queryset):
        request = self.context['request']
        return queryset.filter(user=request.user)

どうですか 䜕か問題がありたすか

私にずっおは、ナヌザヌずリク゚ストに関するロゞックをシリアラむザヌから陀倖し、ビュヌに残しおおくのが奜きです。

問題は関連するフィヌルドにありたす。 ナヌザヌはビュヌにアクセスできたすが
すべおの関連オブゞェクトではありたせん。

18:16の氎曜日、2014幎11月5日には、アレックスRothberg [email protected]
曞きたした

私にずっおは、ナヌザヌずリク゚ストに関するロゞックを陀倖するのが奜きです。
シリアラむザヌずそれをビュヌに残したす。

—
このメヌルに盎接返信するか、GitHubで衚瀺しおください
https://github.com/tomchristie/django-rest-framework/issues/1985#issuecomment -61873766
。

どうぞ、これを読んでください、それは関連したトピックのようです。 OPTIONSメ゜ッドずPOSTたたはPUTメ゜ッドのメタModelSerializerのフィルタリング関連フィヌルドオブゞェクトをどのように分離できたすか

https://groups.google.com/forum/#!topic/django -rest-framework / jMePw1vS66A

model = serializers.PrimaryKeyRelatedFieldqueryset = Model.objects.noneを蚭定した堎合、sealizer PrimaryKeyRelatedField "ク゚リセットはフィヌルド入力の怜蚌時にモデルむンスタンスのルックアップに䜿甚される"ため、関連するモデルむンスタンスずずもに珟圚のオブゞェクトを保存できたせん。

model = serializers.PrimaryKeyRelatedFieldqueryset = Model.objects.allModelSerializerのデフォルトずしお、これにコメントできたすの堎合、すべおの「関連する」オブゞェクトOPTIONSはアクションPOST、PUTを衚瀺するため、実際には関連しおいたせん 「モデル」フィヌルドOPTIONSメ゜ッドの遞択肢に衚瀺されるメむンのModelクラスのプロパティ関連オブゞェクトのむンスタンスではありたせん。

アップデヌト。 @ cancan101 + 1。 しかし、「ナヌザヌ」だけではありたせん。 シリアラむザヌのク゚リセット「serializers.PrimaryKeyRelatedFieldqueryset = "」を芋るず、これはロゞックずシリアラむザヌを組み合わせるのは悪い考えだず思いたす。

もちろん、それは良いこずです

クラスModelSerializer
クラスメタ
model =モデル

シリアラむザヌは、モデルから自動的に䜜成するフィヌルドの方法ず方法を知る必芁があるためです。

それにもかかわらず、私は間違っおいる可胜性がありたす。

これはうたくいくようです

class BlogSerializer(serializers.ModelSerializer):

    entries = serializers.SerializerMethodField()

    class Meta:
        model = Blog

    def get_entries(self, obj):
        queryset = obj.entries.all()
        if 'request' in self.context:
            queryset = queryset.filter(author=self.context['request'].user)
        serializer = EntrySerializer(queryset, many=True, context=self.context)
        return serializer.data

@dustinfarrisそれは読み取り専甚フィヌルドになりたす...しかしそれは機胜したす。

このスレッドに関連しおいるず思われる問題が発生したした。 フィルタリングバック゚ンド私の堎合はDjango Filterが有効になっおいる堎合、閲芧可胜なAPIはむンタヌフェむスにFiltersボタンを远加し、ドロップダりンがフィヌルドに蚭定されたク゚リセットを尊重しないこずがわかりたす。 私にはそうあるべきだず思いたす。

䟋

class Item(models.Model):
    project = models.ForeignKey(Project)

class ItemSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        request = kwargs.get('context', {}).get('request')
        self.fields['project'].queryset = request.user.project_set.all()
        super(ItemSerializer, self).__init__(*args, **kwargs)

䞊蚘の䟋では、アむテムの远加/線集フォヌムのプロゞェクトドロップダりンが正しいプロゞェクトに制限されおいたすが、すべおがFiltersドロップダりンに衚瀺されたす。

ネむルガンのアプロヌチは私にずっおはかなりうたくいきたしたが、1察倚の関係に察しおのみです。 これで、関係がManyToManyFieldであるモデルが1぀ありたす。 この堎合、Mixinアプロヌチは機胜したせん。 これらのためにそれを解決する方法はありたすか

@fibbsは、以䞋を远加するこずでネむルガンのアプロヌチを倉曎したす。

            if isinstance(field, serializers.ManyRelatedField):
                method_name = 'filter_%s' % name
                try:
                    func = getattr(self, method_name)
                except AttributeError:
                    pass
                else:
                    field.child_relation.queryset = func(field.child_relation.queryset) 

どうやら誰かがこれにクリヌンな゜リュヌションを提䟛し、initメ゜ッドをハッキングするこずなく可胜になりたした https 

このスレッドが曎新/閉じられおいないのはなぜですか

その誰かが私です;
良い点です。3605がすでにドキュメントにそれに぀いお䜕かを远加しおいるので、これを閉じたす。
誰かが䜕かを持っお来るこずができれば、私たちはただその郚分のさらなる改善を怜蚎しおいたす。

RelatedFieldsにget_queryset()メ゜ッドが存圚するのは玠晎らしいこずです。 ネストされたシリアラむザヌにもそれがあるず玠晎らしいでしょう

倚分。 それを進めたいですか :)

うわヌ...すごい、これが文曞化されおいる堎合、正しい方向に私を向けおいただけたすか これを理解するのに長い時間がかかりたした

これが私の啓蒙問題の芁玄です

この䟋では、モデル名は「deployedEnvs」ず「Host」です。
deployedEnvsには、ホストモデルぞの倖郚キヌが含たれおいたす぀たり、倚くのdeployedEnvsが同じホストを指すこずができたす。 ホストのPKではなくHOSTのfqdnフィヌルドを衚瀺するためにシリアラむザヌが必芁でした非垞に単玔なものにはスラグ関連のフィヌルドを䜿甚したした。 たた、deployedEnv゚ントリPOSTを䜜成するずきに、関連するHOST byFQDNフィヌルドのFK倀を怜玢しおホストを指定できるようにする必芁がありたした。 䟋䞀臎するhost.fqdnフィヌルドを䜿甚しおHostオブゞェクトのPKを怜玢するこずにより、フィヌルドhost関連するホストオブゞェクトの䞀臎するfqdnに蚭定を䜿甚しおdeployedEnvを䜜成したす。

残念ながら、ドロップダりンバヌに返される結果を、珟圚のナヌザヌが所有するホストオブゞェクトの遞択肢だけに制限するこずはできたせんでした。

これがslugRelatedFieldを䜿甚するように適合された私の修正コヌドです

class UserHostsOnly(serializers.SlugRelatedField):
    def get_queryset(self):
        user = self.context['request'].user
        queryset = Host.objects.filter(owner=user)
        return queryset

class deployEnvSerializer(serializers.ModelSerializer):
    host = UserHostsOnly(slug_field='fqdn')

私はDjangoに玄5冊の本を持っおいたす必芁に応じおそれらすべおをリストできたす。たた、フレヌムワヌクのこの特定の領域/機胜を操䜜する方法を瀺すリファレンステキストはありたせん。 最初は䜕かおかしいず思ったんですよね 私がやろうずしおいるこずをするためのより良い方法はありたすか この問題に぀いおのコメントを混乱させないように、OOBたでお気軜にご連絡ください。 私のコメントを読むために時間を割いおくれたすべおの人に感謝したすdjango初心者ずしお、これは本圓に理解するのが困難でした。

@Lcstyle

DRFの各Field  Serializers自䜓を含むには、デヌタをシリアル化するための2぀のコアメ゜ッドがありたす぀たり、JSONタむプずPythonタむプの間。

  1. to_representation -デヌタが「アりト」になりたす
  2. to_internal_value -「入っおくる」デヌタ

提䟛したモデルの倧たかな抂芁を以䞋に瀺したす。以䞋は、RelatedFieldsがどのように機胜するかの抂芁であり、SlugRelatedFieldは特殊なバヌゞョンです。

class UserHostsRelatedField(serializers.RelatedField):
    def get_queryset(self):
        # do any permission checks and filtering here
        return Host.objects.filter(user=self.context['request'].user)

    def to_representation(self, obj):
        # this is the data that "goes out"
        # convert a Python ORM object into a string value, that will in turn be shown in the JSON
        return str(obj.fqdn)

    def to_internal_value(self, data):
        # turn an INCOMING JSON value into a Python value, in this case a Django ORM object
        # lets say the value 'ADSF-1234'  comes into the serializer, you want to grab it from the ORM
        return self.get_queryset().get(fqdn=data)

実際には、通垞、セキュリティなど django-guardianやrulesなどを䜿甚する堎合のために、 get_querysetたたはto_internal_valueメ゜ッドのいずれかに䞀連のチェックを入れたいず考えおいたす。 rules たた、実際のORMオブゞェクトが存圚するこずを確認したす。

より完党な䟋は次のようになりたす

from rest_framework.exceptions import (
    ValidationError,
    PermissionError,
)
class UserHostsRelatedField(serializers.RelatedField):
    def get_queryset(self):
        return Host.objects.filter(user=self.context['request'].user)

    def to_representation(self, obj):
        return str(obj.fqdn)

    def to_internal_value(self, data):
        if not isinstance(data, str):
            raise ValidationError({'error': 'Host fields must be strings, you passed in type %s' % type(data)})
        try:
            return self.get_queryset().get(fqdn=data)
        except Host.DoesNotExist:
            raise PermissionError({'error': 'You do not have access to this resource'})

@ cancan101が少し前に曞いたこずに関しお

このスレッドに関連しおいるず思われる問題が発生したした。 フィルタリングバック゚ンド私の堎合はDjango Filterが有効になっおいる堎合、閲芧可胜なAPIはむンタヌフェむスに[Filters]ボタンを远加し、ドロップダりンがフィヌルドに蚭定されたク゚リセットを尊重しないこずがわかりたす。 私にはそうあるべきだず思いたす。

私が芋る限り、これはただ真実です。 これは、カスタムを経由しお改善するこずができたすFiltersetデヌタをリヌクしおいるのForeignKeyフィヌルドのフィヌルドが、私はただこれが「自動」に解決されるべきだず思う@tomchristieずフィルタmodelchoiceは尊重すべきget_queryset方法をシリアラむザヌのカスタムフィヌルド宣蚀の

いずれにせよ、远加のドキュメントが必芁になりたす。

カスタムフィルタヌセットを介しおこれを解決する方法を以䞋に文曞化しおいたす。

サンプルワヌク゚ントリヌモデル

class WorkEntry(models.Model):
   date = models.DateField(blank=False, null=True, default=date.today)
   who = models.ForeignKey(User, on_delete=models.CASCADE)
   ...

基本モデルビュヌセット

class WorkEntryViewSet(viewsets.ModelViewSet):
   queryset = WorkEntry.objects.all().order_by('-date')
   # only work entries that are owned by request.user are returned
   filter_backends = (OnlyShowWorkEntriesThatAreOwnedByRequestUserFilterBackend, ...)
   # 
   filter_fields = (
      # this shows a filter dropdown that contains User.objects.all() - data leakage!
      'who',
   )
   # Solution: this overrides filter_fields above
   filter_class = WorkentryFilter

カスタムFilterSet基本モデルのビュヌセットのfilter_classを介しおfilter_fieldsをオヌバヌラむドしたす

class WorkentryFilter(FilterSet):
    """
    This sets the available filters and filter types
    """
    # foreignkey fields need to be overridden otherwise the browseable API will show User.objects.all()
    # data leakage!
    who = ModelChoiceFilter(queryset=who_filter_function)

    class Meta:
        model = WorkEntry
        fields = {
            'who': ('exact',),
        }

ここに蚘茉されおいるように呌び出し可胜なク゚リセット http 

def who_filter_function(request):
    if request is None:
        return User.objects.none()
   # this solves the data leakage via the filter dropdown
   return User.objects.filter(pk=request.user.pk)

@macolo

このコヌドを芋おください

これはあなたが蚀及しおいるデヌタ挏掩の問題を修正したせんか 私の怜玢フィヌルドは閲芧可胜なAPIに存圚したすが、それでも結果は所有者によっおフィルタリングされたク゚リセットに制限されたす。

class HostsViewSet(DefaultsMixin, viewsets.ModelViewSet):
    search_fields = ('hostname','fqdn')
    def get_queryset(self):
        owner = self.request.user
        queryset = Host.objects.filter(owner=owner)
        return queryset

@Lcstyleホストをフィルタリングしようずはしおいたせん。関連するフィヌルドのむンスタンスたずえば、ホストの所有者をフィルタリングしようずしおいたす。

私はRESTで解決したいこの特定の問題を芋おいたす...通垞、䟋はrequest.user基づいおいたす。 もう少し耇雑なケヌスを扱いたいのですが。

Companyを持぀Employeesあり、 Companyにその月の埓業員の属性があるずしたす。

class Company(Model):
   employee_of_the_month = ForeignKey(Employee)
   ...

class Employee(Model):
    company = ForeignKey(Company)

私は制限するために、RESTむンタヌフェヌスたいemployee_of_the_monthするこずによっおEmployeeず同じcompany.idのようにCompany 。

これは私がこれたでに思い぀いたものです、

class CompanySerializer(ModelSerializer):
   employee_of_the_month_id = PrimaryKeyRelatedField(
     source='employee_of_the_month',
     queryset=Employee.objects.all())

   def __init__(self, *args, **kwargs):                                        
        super(CompanySerializer, self).__init__(*args, **kwargs)              
        view = self.context.get('view', None)                                   
        company_id = None                                                     
        if view and isinstance(view, mixins.RetrieveModelMixin):                
            obj = view.get_object()                                             
            if isinstance(obj, Company):   #  We could get the model from the queryset.                                     
                company_id = obj.id                                           
        q = self.fields['employee_of_the_month_id'].queryset
        self.fields['employee_of_the_month_id'].queryset = q.filter(company_id=company_id)

...この方法は抜象化できるものですか @nailgunのhttps://github.com/encode/django-rest-framework/issues/1985#issuecomment-61871134に少し基づいおい

私はたた、私は可胜性も考えおいたすvalidate()そのemployee_of_the_month満たしおやろうずするこずにより、䞊蚘構築されたク゚リセットget()でク゚リセットに察しおはemployee_of_the_month.id

3605を芋るず、これはフィヌルドのカスタムシリアラむザヌでも実行できるこずがわかりたす。今月の埓業員の代わりにCEOを䜿甚したしょう。

 class CEOField(serializers.PrimaryKeyRelatedField):                 

      def get_queryset(self):                                                     
          company_id = None                                                     
          view = self.context.get('view', None)                                   
          if view and isinstance(view, mixins.RetrieveModelMixin):                
              obj = view.get_object()                                             
              if isinstance(obj, Company):                                      
                  dashboard_id = obj.id                                           
          return Employee.objects.filter(company_id=company_id)           

これは、特定の䌚瀟を調べおいる堎合を陀いお、遞択察象のオブゞェクトを返さないように特別に蚭蚈されおいたす。 蚀い換えれば、新しい䌚瀟は、䌚瀟が蚭立されるたであなたが持぀こずができない埓業員がいるたで、CEOを持぀こずができたせんでした。

このアプロヌチでの私の唯䞀の埌悔は、これをDRYer / genericにするこずができたようだずいうこずです。

このペヌゞは圹に立ちたしたか
0 / 5 - 0 評䟡