Django-rest-framework: ๊ด€๋ จ ํ•„๋“œ์— ๋Œ€ํ•œ ๊ถŒํ•œ ๋ฐ ํ•„ํ„ฐ๋ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฌธ์„œํ™”ํ•ฉ๋‹ˆ๋‹ค.

์— ๋งŒ๋“  2014๋…„ 10์›” 23์ผ  ยท  26์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: encode/django-rest-framework

ํ˜„์žฌ ๊ด€๊ณ„๋Š” ๋ณด๊ธฐ์— ์ ์šฉ๋˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๊ถŒํ•œ ๋ฐ ํ•„ํ„ฐ๋ง ์ง‘ํ•ฉ์„ ์ž๋™์œผ๋กœ ์ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ด€๊ณ„์— ๋Œ€ํ•œ ๊ถŒํ•œ์ด๋‚˜ ํ•„ํ„ฐ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ช…์‹œ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ์ธ์ ์œผ๋กœ ์ด ๋ฌธ์ œ๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์—†์ง€๋งŒ ์ ์–ด๋„ ๋ฌธ์„œํ™”๋ฅผ ๋” ์ž˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋ถ„๋ช…ํžˆ ์žˆ์Šต๋‹ˆ๋‹ค.

์ง€๊ธˆ ์ œ ์˜๊ฒฌ์€ ์šฐ๋ฆฌ๊ฐ€ ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ ์‚ฌ๋ก€๋ฅผ ์ œ์‹œํ•˜๊ณ  ์ด๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฌธ์„œํ™”ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ชจ๋“  ์ž๋™ ์ฝ”๋“œ๋Š” ํƒ€์‚ฌ ํŒจํ‚ค์ง€ ์ž‘์„ฑ์ž๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋‚จ๊ฒจ๋‘์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ๊ธฐ์—ฌ์ž๊ฐ€ ๋ฌธ์ œ๋ฅผ ํƒ์ƒ‰ํ•˜๊ณ  ์ž ์žฌ์ ์œผ๋กœ ํ•ต์‹ฌ์— ํฌํ•จ๋  ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ์†”๋ฃจ์…˜์„ ์ƒ๊ฐํ•ด ๋‚ผ ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฏธ๋ž˜์— ์ด ๋ฌธ์ œ๋Š” '๋ฌธ์„œ'์—์„œ 'ํ–ฅ์ƒ'์œผ๋กœ ์Šน๊ฒฉ๋  ์ˆ˜ ์žˆ์ง€๋งŒ ์ œ3์ž ํŒจํ‚ค์ง€์— ์˜ํ•ด ๋ฐฑ์—…๋˜๋Š” ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ์ด ์—†๋Š” ํ•œ ์ด ์ƒํƒœ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

Documentation

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์ด ๊ฐ„๋‹จํ•œ ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ ๋ฏน์Šค์ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ด€๋ จ ํ•„๋“œ์˜ ์ฟผ๋ฆฌ ์„ธํŠธ๋ฅผ ํ•„ํ„ฐ๋งํ–ˆ์Šต๋‹ˆ๋‹ค.

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_ ๊ฐœ์ฒด์— ๋Œ€ํ•œ ๊ด€๊ณ„์ด๋ฏ€๋กœ ํ˜„์žฌ ๋ณด๊ธฐ์˜ ๊ถŒํ•œ ๋ฐ ํ•„ํ„ฐ ํด๋ž˜์Šค๊ฐ€ ๊ฐœ์ฒด ๊ด€๊ณ„์— ์ ์šฉํ•˜๋ ค๋Š” ๊ทœ์น™๊ณผ ๋ฐ˜๋“œ์‹œ โ€‹โ€‹๋™์ผํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

ํ•˜์ดํผ๋งํฌ๋œ ๊ด€๊ณ„์˜ ๊ฒฝ์šฐ ์ด๋ก ์ ์œผ๋กœ ๊ทธ๋“ค์ด (+)๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๋ณด๊ธฐ๋ฅผ ๊ฒฐ์ •ํ•˜๊ณ  ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•„ํ„ฐ๋ง/๊ถŒํ•œ์„ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ํ™•์‹คํžˆ ๋”์ฐํ•˜๊ฒŒ ๋ฐ€์ ‘ํ•˜๊ฒŒ ์—ฐ๊ฒฐ๋œ ๋””์ž์ธ์œผ๋กœ ๋๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ดํผ๋งํฌ๋˜์ง€ ์•Š์€ ๊ด€๊ณ„์˜ ๊ฒฝ์šฐ์—๋Š” ๊ทธ๋ ‡๊ฒŒ ํ•  ์ˆ˜๋„ ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ ๋ชจ๋ธ์ด ๋‹จ์ผ ํ‘œ์ค€ ๋ณด๊ธฐ์— ํ•œ ๋ฒˆ ๋…ธ์ถœ๋œ๋‹ค๋Š” ๋ณด์žฅ์€ ์—†์œผ๋ฏ€๋กœ ํ•˜์ดํผ๋งํฌ๋˜์ง€ ์•Š์€ ๊ด€๊ณ„์— ์‚ฌ์šฉํ•  ๊ถŒํ•œ์„ ์ž๋™์œผ๋กœ ๊ฒฐ์ •ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

(+) ์‹ค์ œ๋กœ๋Š” _ํ•ฉ๋ฆฌ์ ์ธ_ ๋ฐฉ์‹์œผ๋กœ ๊ทธ๋ ‡๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ์‹ค์ œ๋กœ ๊ฐ€๋Šฅํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ž ์‹œ ๋™์•ˆ ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์•„๋งˆ๋„ "has__permission"? ๊ทธ๋Ÿฌ๋ฉด ๊ฐ ๊ถŒํ•œ ๊ฐœ์ฒด๊ฐ€ ๊ด€๋ จ ๊ฐœ์ฒด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ๋žŒ๋“ค์€ ํ•„ํ„ฐ๋ง์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๊นŒ? ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ์ด ์—†๋Š” ๊ฐœ์ฒด๋ฅผ ์ˆจ๊ธฐ๋Š” ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ? ๊ทธ๊ฒƒ์ด ์œ ์Šค ์ผ€์ด์Šค๋ผ๋ฉด ํ•„ํ„ฐ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ฐธ์กฐ๋œ ๋ฌธ์ œ #1646 ์ค‘ ํ•˜๋‚˜๋Š” ๊ด€๋ จ ํ•„๋“œ์— ๋Œ€ํ•ด ํƒ์ƒ‰ ๊ฐ€๋Šฅํ•œ API ํŽ˜์ด์ง€์— ํ‘œ์‹œ๋˜๋Š” ์„ ํƒ์„ ์ œํ•œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ €๋Š” ํƒ์ƒ‰ ๊ฐ€๋Šฅํ•œ API๋ฅผ ์ข‹์•„ํ•˜๊ณ  ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž์ธ ์ €๋ฟ๋งŒ ์•„๋‹ˆ๋ผ REST API์˜ ํ”„๋ŸฐํŠธ ์—”๋“œ ๊ฐœ๋ฐœ์ž/์‚ฌ์šฉ์ž์—๊ฒŒ๋„ ํ›Œ๋ฅญํ•œ ๋„๊ตฌ๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํƒ์ƒ‰ ๊ฐ€๋Šฅํ•œ API๊ฐ€ ์ผœ์ง„ ์ƒํƒœ๋กœ ์ œํ’ˆ์„ ๋ฐฐ์†กํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค(์ฆ‰, DEBUG ๋ชจ๋“œ์—์„œ ์‚ฌ์ดํŠธ๊ฐ€ ์™ธ๋กญ์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋„ ์‹คํ–‰๋จ). ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด ํƒ์ƒ‰ ๊ฐ€๋Šฅํ•œ API ํŽ˜์ด์ง€๋ฅผ ํ†ตํ•ด ์ •๋ณด๊ฐ€ ๋ˆ„์ถœ๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (๋ฌผ๋ก  ํ•ด๋‹น ํŽ˜์ด์ง€๊ฐ€ ์ผ๋ฐ˜์ ์œผ๋กœ ํ”„๋กœ๋•์…˜ ์ค€๋น„๊ฐ€ ๋˜์–ด ์žˆ๊ณ  ์•ˆ์ „ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์š”๊ตฌ ์‚ฌํ•ญ์— ์ถ”๊ฐ€).

์ด๊ฒƒ์ด ์˜๋ฏธํ•˜๋Š” ๋ฐ”๋Š” ๊ด€๋ จ ํ•„๋“œ์˜ ์กด์žฌ์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ POST๋ฅผ ํ†ตํ•ด ๋ฐฐ์šธ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋ณด๋‹ค HTML ํŽ˜์ด์ง€๋ฅผ ํ†ตํ•ด ๋” ์ด์ƒ ๋ฐฐ์šธ ์ˆ˜ ์—†์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ฒฐ๊ตญ ๊ด€๋ จ ํ•„๋“œ์˜ View๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„ํ„ฐ๋ง์„ ์ œ๊ณตํ•˜๋Š” ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ์šฉ ๋ฏน์Šค์ธ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

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

Serializer์™€ View๋ฅผ ํ˜ผํ•ฉํ•˜๋Š” ๋Œ€์‹  View mixin: #1935๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์ „์ด ํ•„์š”ํ•˜์ง€ ์•Š๊ณ  View์—์„œ secured_fields ๋ชฉ๋ก์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ฐ„๋‹จํ•œ ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ ๋ฏน์Šค์ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ด€๋ จ ํ•„๋“œ์˜ ์ฟผ๋ฆฌ ์„ธํŠธ๋ฅผ ํ•„ํ„ฐ๋งํ–ˆ์Šต๋‹ˆ๋‹ค.

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)

์–ด๋•Œ์š”? ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๊ณ  ๋ณด์‹ญ๋‹ˆ๊นŒ?

์ €์—๊ฒŒ๋Š” ์‚ฌ์šฉ์ž ๋ฐ ์š”์ฒญ์— ๋Œ€ํ•œ ๋…ผ๋ฆฌ๋ฅผ Serializer ์™ธ๋ถ€๋กœ ์œ ์ง€ํ•˜๊ณ  View์— ๋‚จ๊ฒจ๋‘๋Š” ๊ฒƒ์„ ์ข‹์•„ํ•ฉ๋‹ˆ๋‹ค.

๋ฌธ์ œ๋Š” ๊ด€๋ จ ๋ถ„์•ผ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋ณด๊ธฐ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์ง€๋งŒ
๋ชจ๋“  ๊ด€๋ จ ๊ฐœ์ฒด๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.

2014๋…„ 11์›” 5์ผ ์ˆ˜์š”์ผ ์˜คํ›„ 6์‹œ 16๋ถ„, Alex Rothberg [email protected]
์ผ๋‹ค:

์ €์—๊ฒŒ๋Š” ์‚ฌ์šฉ์ž์™€ ์š”์ฒญ์— ๋Œ€ํ•œ ๋…ผ๋ฆฌ๋ฅผ
Serializer๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  View์— ๊ทธ๋Œ€๋กœ ๋‘ก๋‹ˆ๋‹ค.

โ€”
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ฑฐ๋‚˜ GitHub์—์„œ ํ™•์ธํ•˜์„ธ์š”.
https://github.com/tomchristie/django-rest-framework/issues/1985#issuecomment -61873766
.

๊ด€๋ จ ์ฃผ์ œ์ธ ๊ฒƒ ๊ฐ™์œผ๋‹ˆ ์ฝ์–ด์ฃผ์„ธ์š”. OPTIONS ๋ฉ”์„œ๋“œ์™€ POST ๋˜๋Š” PUT ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด Meta(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 ๋ฉ”์„œ๋“œ)์— ๋Œ€ํ•œ ์„ ํƒ ํ•ญ๋ชฉ์— ํ‘œ์‹œ๋˜๋Š” ๊ธฐ๋ณธ ๋ชจ๋ธ ํด๋ž˜์Šค์˜ ์†์„ฑ(๊ด€๋ จ ๊ฐœ์ฒด๊ฐ€ ์žˆ๋Š” ์ธ์Šคํ„ด์Šค๊ฐ€ ์•„๋‹˜).

์—…๋ฐ์ดํŠธ. @cancan101 +1 . ๊ทธ๋Ÿฌ๋‚˜ "์‚ฌ์šฉ์ž"๋งŒ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ์—์„œ ์ฟผ๋ฆฌ ์ง‘ํ•ฉ์„ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ์ด ์ด๊ฒƒ์€ ๋…ผ๋ฆฌ์™€ ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ๋ฅผ ํ˜ผํ•ฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์ง€ ์•Š๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. "serializers.PrimaryKeyRelatedField(queryset=".

๋ฌผ๋ก  ์ข‹์Šต๋‹ˆ๋‹ค.

ํด๋ž˜์Šค ModelSerializer:
ํด๋ž˜์Šค ๋ฉ”ํƒ€:
๋ชจ๋ธ=๋ชจ๋ธ

Serializer๋Š” 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 ๋“œ๋กญ๋‹ค์šด์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

nailgun์˜ ์ ‘๊ทผ ๋ฐฉ์‹์€ ์ €์—๊ฒŒ ๊ฝค ํšจ๊ณผ๊ฐ€ ์žˆ์—ˆ์ง€๋งŒ ์ผ๋Œ€๋‹ค ๊ด€๊ณ„์—์„œ๋งŒ ์ž‘๋™ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ๋‚ด ๊ด€๊ณ„๊ฐ€ ManyToManyField์ธ ๋ชจ๋ธ์ด ํ•˜๋‚˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ Mixin ๋ฐฉ์‹์€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์•„์ด๋””์–ด๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

@fibbs๋Š” ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•˜์—ฌ nailgun์˜ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

            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) 

๋ถ„๋ช…ํžˆ ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์ด์— ๋Œ€ํ•œ ๊นจ๋—ํ•œ ์†”๋ฃจ์…˜์„ ์ œ๊ณตํ–ˆ์œผ๋ฉฐ ์ด์ œ ์ดˆ๊ธฐํ™” ๋ฐฉ๋ฒ•์„ ํ•ดํ‚นํ•˜์ง€ ์•Š๊ณ ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. https://medium.com/django-rest-framework/limit-related-data-choices-with-django-rest-framework-c54e96f5815e

์ด ์Šค๋ ˆ๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ/๋‹ซํžˆ์ง€ ์•Š์€ ์ด์œ ๊ฐ€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.

๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๋‚˜๋ผ๋Š” ๊ฒƒ์„ ;)
์ข‹์€ ์ง€์ ์ž…๋‹ˆ๋‹ค. #3605๊ฐ€ ์ด๋ฏธ ๋ฌธ์„œ์— ์ด์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์ด ๊ธ€์„ ๋‹ซ๊ฒ ์Šต๋‹ˆ๋‹ค.
๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๋ญ”๊ฐ€๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ฌ ์ˆ˜ ์žˆ๋‹ค๋ฉด ์šฐ๋ฆฌ๋Š” ์—ฌ์ „ํžˆ ๊ทธ ๋ถ€๋ถ„์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๊ฐœ์„ ์„ ๊ณ ๋ คํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด์ œ get_queryset() ๋ฉ”์†Œ๋“œ๊ฐ€ RelatedFields์— ๋Œ€ํ•ด ์กด์žฌํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ค‘์ฒฉ๋œ ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ์ •๋ง ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค!

์•„๋งˆ. ๊ณ„์† ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? :)

์™€... ๊ต‰์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๋ฌธ์„œํ™”๋˜์–ด ์žˆ๋‹ค๋ฉด ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉํ–ฅ์œผ๋กœ ์•ˆ๋‚ดํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ์ด๊ฑฐ ์•Œ์•„๋‚ด๋Š”๋ฐ ์˜ค๋ž˜๊ฑธ๋ ธ์–ด์š”!

๋‹ค์Œ์€ ๊ตํ™”๋ฅผ ์œ„ํ•œ ๋‚ด ๋ฌธ์ œ์˜ ์š”์•ฝ์ž…๋‹ˆ๋‹ค.

์ด ์˜ˆ์—์„œ ๋‚ด ๋ชจ๋ธ ์ด๋ฆ„์€ "deployedEnvs" ๋ฐ "Host"์ž…๋‹ˆ๋‹ค.
deployEnvs์—๋Š” ํ˜ธ์ŠคํŠธ ๋ชจ๋ธ์— ๋Œ€ํ•œ ์™ธ๋ž˜ ํ‚ค๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค(์ฆ‰, ์—ฌ๋Ÿฌ deployEnvs๊ฐ€ ๋™์ผํ•œ ํ˜ธ์ŠคํŠธ๋ฅผ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์žˆ์Œ). ํ˜ธ์ŠคํŠธ์— ๋Œ€ํ•œ PK๊ฐ€ ์•„๋‹Œ HOST์˜ fqdn ํ•„๋“œ๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ๊ฐ€ ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค(๋งค์šฐ ๊ฐ„๋‹จํ•œ ์Šฌ๋Ÿฌ๊ทธ ๊ด€๋ จ ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค). ๋˜ํ•œ FQDN ํ•„๋“œ๋กœ ๊ด€๋ จ HOST์— ๋Œ€ํ•œ FK ๊ฐ’์„ ์กฐํšŒํ•˜์—ฌ ํ˜ธ์ŠคํŠธ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด deployEnv ํ•ญ๋ชฉ(POST)์„ ์ƒ์„ฑํ•  ๋•Œ ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ: ์ผ์น˜ํ•˜๋Š” host.fqdn ํ•„๋“œ๊ฐ€ ์žˆ๋Š” ํ˜ธ์ŠคํŠธ ๊ฐœ์ฒด์˜ PK๋ฅผ ์กฐํšŒํ•˜์—ฌ ํ•„๋“œ ํ˜ธ์ŠคํŠธ(๊ด€๋ จ ํ˜ธ์ŠคํŠธ ๊ฐœ์ฒด์˜ ์ผ์น˜ํ•˜๋Š” fqdn์œผ๋กœ ์„ค์ •)์™€ ํ•จ๊ป˜ deployEnv๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๋ถˆํ–‰ํžˆ๋„ ํ˜„์žฌ ์‚ฌ์šฉ์ž๊ฐ€ ์†Œ์œ ํ•œ ํ˜ธ์ŠคํŠธ ๊ฐœ์ฒด์— ๋Œ€ํ•œ ์„ ํƒ ํ•ญ๋ชฉ์œผ๋กœ๋งŒ ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ์ค„์— ๋ฐ˜ํ™˜๋œ ๊ฒฐ๊ณผ๋ฅผ ์ œํ•œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

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)

์‹ค์ œ๋กœ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ณด์•ˆ๊ณผ ๊ฐ™์€ ๊ฒƒ์„ ์œ„ํ•ด get_queryset ๋˜๋Š” to_internal_value ๋ฉ”์„œ๋“œ์— ๋งŽ์€ ์ˆ˜ํ‘œ๋ฅผ ๋„ฃ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค( django-guardian ๋˜๋Š” rules ์™€ ๊ฐ™์€ ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

๋” ์™„์ „ํ•œ ์˜ˆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

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๊ฐ€ ์ธํ„ฐํŽ˜์ด์Šค์— ํ•„ํ„ฐ ๋ฒ„ํŠผ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๋“œ๋กญ๋‹ค์šด์ด ํ•„๋“œ์— ์„ค์ •๋œ ์ฟผ๋ฆฌ ์„ธํŠธ๋ฅผ ์กด์ค‘ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์•ผ ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ๋ณผ ์ˆ˜์žˆ๋Š” ํ•œ ์ด๊ฒƒ์€ ์—ฌ์ „ํžˆ โ€‹โ€‹์‚ฌ์‹ค์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ˆ„์ถœ๋˜๋Š” foreignkey ํ•„๋“œ์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ Filterset ํ•„๋“œ๋ฅผ ํ†ตํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ @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

Custom 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://django-filter.readthedocs.io/en/latest/ref/filters.html#modelchoicefilter

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 ๋Š” AS 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๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ๋˜ํ•œ ์ƒ๊ฐํ•˜๊ณ  ๊ทธ I ์ˆ˜๋„ 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๊ฐ€ ์žˆ์„ ์ˆ˜ ์—†์—ˆ๊ณ  ํšŒ์‚ฌ๊ฐ€ ๋งŒ๋“ค์–ด์ง€๊ธฐ ์ „์—๋Š” CEO๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

์ด ์ ‘๊ทผ ๋ฐฉ์‹์— ๋Œ€ํ•œ ๋‚˜์˜ ์œ ์ผํ•œ ํ›„ํšŒ๋Š” ์ด๊ฒƒ์ด DRYer/generic์œผ๋กœ ๋งŒ๋“ค์–ด์งˆ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰