Django-rest-framework: TypeError ํ•ด์‹œํ•  ์ˆ˜ ์—†๋Š” ์œ ํ˜•: ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ์˜ RelatedField๊ฐ€ ์žˆ๋Š” 'dict'

์— ๋งŒ๋“  2017๋…„ 04์›” 28์ผ  ยท  3์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: encode/django-rest-framework

์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • [x] Django REST ํ”„๋ ˆ์ž„์›Œํฌ์˜ master ๋ถ„๊ธฐ์— ํ•ด๋‹น ๋ฌธ์ œ๊ฐ€ ์žˆ์Œ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.
  • [x] ๊ณต๊ฐœ ํ‹ฐ์ผ“๊ณผ ๋น„๊ณต๊ฐœ ํ‹ฐ์ผ“ ๋ชจ๋‘์—์„œ ์œ ์‚ฌํ•œ ๋ฌธ์ œ๋ฅผ ๊ฒ€์ƒ‰ํ–ˆ์ง€๋งŒ ์ค‘๋ณต์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  • [x] ์ด๊ฒƒ์€ ์‚ฌ์šฉ ์งˆ๋ฌธ์ด ์•„๋‹™๋‹ˆ๋‹ค. (๋Œ€์‹  ํ† ๋ก  ๊ทธ๋ฃน์œผ๋กœ ๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.)
  • [x] ์ด๊ฒƒ์€ ํƒ€์‚ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ์ œ3์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ˜•ํƒœ์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค.)
  • [x] ๋ฌธ์ œ๋ฅผ ๊ฐ€๋Šฅํ•œ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๊ฒฝ์šฐ๋กœ ์ค„์˜€์Šต๋‹ˆ๋‹ค.
  • [ ] ํ’€ ๋ฆฌํ€˜์ŠคํŠธ๋กœ ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ํฌํ•จ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. (๊ทธ๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ์—๋„ ๋ฌธ์ œ๋ฅผ ์ˆ˜๋ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

์•”ํ˜ธ

from rest_framework import serializers
from incidents.models import Incident

class NodeDataField(serializers.RelatedField):

    def to_representation(self, node):
        return {
            "id": node.id,
            "name": node.name,
            "ipaddress": node.ipv4
        }

    def to_internal_value(self, id):
        pass

์ด๊ฒƒ์€ /api/v1/incidents/์— TypeError๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
ํ•ด์‹œํ•  ์ˆ˜ ์—†๋Š” ์œ ํ˜•: 'dict'

unhashable type: 'dict'
5       <label class="col-sm-2 control-label {% if style.hide_label %}sr-only{% endif %}">
6         {{ field.label }}
7       </label>
8     {% endif %}
9   
10    <div class="col-sm-10">
11      <select class="form-control" name="{{ field.name }}">
12        {% if field.allow_null or field.allow_blank %}
13          <option value="" {% if not field.value %}selected{% endif %}>--------</option>
14        {% endif %}
15  

      {% for select in field.iter_options %}



16            {% if select.start_option_group %}
17              <optgroup label="{{ select.label }}">
18            {% elif select.end_option_group %}
19              </optgroup>
20            {% else %}
21              <option value="{{ select.value }}" {% if select.value|as_string == field.value|as_string %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
22            {% endif %}
23       {% endfor %}
24     </select>

์Šคํƒ ์ถ”์ 

/opt/grofers/firealarm-api/api/firealarm_api/lib/python3.4/_collections_abc.py in update
                                    self[key] = value
     ...
โ–ถ Local vars
/opt/grofers/firealarm-api/api/firealarm_api/lib/python3.4/collections/__init__.py in __setitem__
            self.__update(*args, **kwds)
        def __setitem__(self, key, value,
                        dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link):
            'od.__setitem__(i, y) <==> od[i]=y'
            # Setting a new item creates a new link at the end of the linked list,
            # and the inherited dictionary is updated with the new key/value pair.
                        if key not in self:
     ...
                self.__map[key] = link = Link()
                root = self.__root
                last = root.prev
                link.prev, link.next, link.key = last, root, key
                last.next = link
                root.prev = proxy(link)
โ–ผ Local vars
Variable    Value
self    
OrderedDict()
dict_setitem    
<slot wrapper '__setitem__' of 'dict' objects>
value   
'Node object'
proxy   
<built-in function proxy>
Link    
<class 'collections._Link'>
key     
{'id': 1, 'ipaddress': '172.31.84.130', 'name': 'abc123.con'}

์žฌํ˜„ ๋‹จ๊ณ„

  1. ์ด ๊ด€๋ จ ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ ํ•„๋“œ๊ฐ€ ์‚ฌ์šฉ๋˜๊ณ  /?format=api๊ฐ€ ์œ„์—์„œ ์„ค๋ช…ํ•œ ์˜ค๋ฅ˜์™€ ํ•จ๊ป˜ ์‹คํŒจํ•˜๋Š” ๋ฐ˜๋ฉด /?format=json ์ž‘๋™์€ ์˜ˆ์ƒ๋Œ€๋กœ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ทฐ์— ๋Œ€ํ•œ GET ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

    ์˜ˆ์ƒ๋˜๋Š” ํ–‰๋™

๋‚˜๋จธ์ง€ ํ”„๋ ˆ์ž„์›Œํฌ์˜ ํƒ์ƒ‰ ๊ฐ€๋Šฅํ•œ API ๋ณด๊ธฐ ํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ œ ํ–‰๋™

/api/v1/incidents/์—์„œ TypeError ์‹คํŒจ
ํ•ด์‹œํ•  ์ˆ˜ ์—†๋Š” ์œ ํ˜•: 'dict'

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

๊ฐ€๋Šฅํ•œ ํ•œ ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ์˜ create ๋ฐ update ๋ฉ”์„œ๋“œ๋ฅผ ๋ฎ์–ด์“ฐ์ง€ ์•Š์œผ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๋‚˜๋Š” ์ด๊ฒƒ์„ ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ์ž‘์€ ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ–ˆ์ง€๋งŒ ์–ด๋–ค ์ด์œ ๋กœ ์ด ํด๋ž˜์Šค์™€ ๊ด€๋ จ๋œ ์˜ค๋ฅ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

class PrimaryKeyField(serializers.RelatedField):
    def __init__(self, serializer, **kwargs):
        self.serializer = serializer(**kwargs)
        if "queryset" not in kwargs:
            kwargs["queryset"] = self.serializer.Meta.model.objects.all()
        self.pk_field = serializers.PrimaryKeyRelatedField(**kwargs)

        super().__init__(**kwargs)

    def to_representation(self, instance):
        return self.serializer.to_representation(instance)

    def to_internal_value(self, data):
        try:
            return self.pk_field.to_internal_value(data["id"])
        except TypeError:
            return data

๋ฌธ์ œ๋Š” to_representation ๋ฉ”์„œ๋“œ์—์„œ unhashable type: 'collections.OrderedDict' ๋ฅผ ์–ป๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์™œ ๊ทธ๋Ÿฐ์ง€ ์ดํ•ด๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  3 ๋Œ“๊ธ€

์•ˆ๋…•ํ•˜์„ธ์š” @manjitkumar - RelatedField ์ผ๋ฐ˜์ ์œผ๋กœ ๊ด€๋ จ ๊ฐœ์ฒด๋ฅผ ๋‹จ์ผ ๊ฐ’(์˜ˆ: ์Šฌ๋Ÿฌ๊ทธ, ๊ธฐ๋ณธ ํ‚ค, URL ๋“ฑ...)์œผ๋กœ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์ค‘์ฒฉ๋œ ๊ฐ์ฒด ํ‘œํ˜„์„ ์ œ๊ณตํ•˜๋ ค๋ฉด ์ค‘์ฒฉ๋œ ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€๋Šฅํ•œ ํ•œ ์ง๋ ฌ ๋ณ€ํ™˜๊ธฐ์˜ create ๋ฐ update ๋ฉ”์„œ๋“œ๋ฅผ ๋ฎ์–ด์“ฐ์ง€ ์•Š์œผ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๋‚˜๋Š” ์ด๊ฒƒ์„ ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ์ž‘์€ ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ–ˆ์ง€๋งŒ ์–ด๋–ค ์ด์œ ๋กœ ์ด ํด๋ž˜์Šค์™€ ๊ด€๋ จ๋œ ์˜ค๋ฅ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

class PrimaryKeyField(serializers.RelatedField):
    def __init__(self, serializer, **kwargs):
        self.serializer = serializer(**kwargs)
        if "queryset" not in kwargs:
            kwargs["queryset"] = self.serializer.Meta.model.objects.all()
        self.pk_field = serializers.PrimaryKeyRelatedField(**kwargs)

        super().__init__(**kwargs)

    def to_representation(self, instance):
        return self.serializer.to_representation(instance)

    def to_internal_value(self, data):
        try:
            return self.pk_field.to_internal_value(data["id"])
        except TypeError:
            return data

๋ฌธ์ œ๋Š” to_representation ๋ฉ”์„œ๋“œ์—์„œ unhashable type: 'collections.OrderedDict' ๋ฅผ ์–ป๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์™œ ๊ทธ๋Ÿฐ์ง€ ์ดํ•ด๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ผ๋ถ€ ์—…๊ทธ๋ ˆ์ด๋“œ ์ œ์•ˆ?
dict ์œ ํ˜•์˜ to_representation()์— ๋Œ€ํ•œ ๋ฐ˜ํ™˜์„ ๊ตฌํ˜„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํŽ˜์ด์ง€์—์„œ ์–‘์‹์„ ์ƒ์„ฑํ•˜๋ ค๊ณ  ํ•  ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
์•„๋งˆ๋„ ๊ทธ๊ฒƒ์€ ๋ถ„๋ช…ํ•˜๊ณ  ์–ด๋ฆฌ์„์€ ์ผ์ด์ง€๋งŒ ๊ธฐ์กด ํด๋ž˜์Šค๋กœ ์•„๋ž˜๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๊นŒ?

class AnyRelatedField(serializers.RelatedField):
    def __init__(self, query_fields=tuple(), **kwargs):
        if not query_fields:
            raise NotImplementedError("AnyRelatedField requires a query field names to be provided as a tuple")

        self.query_fields = query_fields
        self.query_objects = Q()

        super().__init__(**kwargs)

    def to_internal_value(self, data):
        for field in self.query_fields:
            if field in ['pk', 'id'] and not isinstance(data, int):
                continue
            self.query_objects |= Q(**{field: data})
        try:
            self.instance = self.queryset.get(self.query_objects)
            return self.instance
        except self.queryset.model.DoesNotExist:
            raise serializers.ValidationError(
                detail="{} object does not exist with {}: {}".format(
                    self.queryset.model.__name__, ', '.join(self.query_fields), data
                ),
                code=self.field_name
            )

    def to_representation(self, val):
        return val
์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰