Repository: django-json-api/django-rest-framework-json-api

Issue:
Bug with DRF v3.4.4 raises unhashable type: 'OrderedDict' 
I have a simple test setup and I tired using the latest djangorestframework==3.4.4 and it fails. (everything works fine with djangorestframework==3.4.3)

 The error get when trying to open the list view for this model is:
TypeError: unhashable type: 'OrderedDict'

I see they have some recent commits which have the type OrderedDict mentioned, so it looks like they may have inadvertently broken something, not sure...

My setup is:
Requirements:
`
django==1.9
unipath==1.1
mysqlclient==1.3.7
django-admin-bootstrapped==2.5.7
simple-crypt==4.1.7
django-smart-autoregister==0.0.3
django-custom-user==0.6
# djangorestframework==3.4.4  # broken with json api

djangorestframework==3.4.3
djangorestframework-jsonapi==2.0.1
`

Serializers:
`
from app_core.models import UserAccount
from rest_framework_json_api import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserAccount
`

Views:
`
from app_core.models import UserAccount
from rest_framework import viewsets
from API.serializers import UserSerializer

class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = UserAccount.objects.all()
    serializer_class = UserSerializer
`

Urls:
`
from django.conf.urls import url, include
from rest_framework import routers
from API import views as api_views

router = routers.DefaultRouter()
router.register(r'users', api_views.UserViewSet)

urlpatterns = [
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

`

Traceback:
`
Environment:

Request Method: GET
Request URL: http://192.168.99.100:8000/api/users/

Django Version: 1.9
Python Version: 3.4.5
Installed Applications:
('django_admin_bootstrapped',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'custom_user',
 'rest_framework',
 'app_core',
 'website')
Installed Middleware:
('django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.middleware.security.SecurityMiddleware')

Template error:
In template /usr/local/lib/python3.4/site-packages/rest_framework/templates/rest_framework/horizontal/select_multiple.html, error at line 15
   unhashable type: 'OrderedDict'   5 : 
   6 : <div class="form-group">
   7 :   {% if field.label %}
   8 :     <label class="col-sm-2 control-label {% if style.hide_label %}sr-only{% endif %}">
   9 :       {{ field.label }}
   10 :     </label>
   11 :   {% endif %}
   12 : 
   13 :   <div class="col-sm-10">
   14 :     <select multiple {{ field.choices|yesno:",disabled" }} class="form-control" name="{{ field.name }}">
   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 in field.value|as_list_of_strings %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
   22 :         {% endif %}
   23 :       {% empty %}
   24 :           <option>{{ no_items }}</option>
   25 :       {% endfor %}

Traceback:

File "/usr/local/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
1.                     response = self.process_exception_by_middleware(e, request)

File "/usr/local/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
1.                     response = response.render()

File "/usr/local/lib/python3.4/site-packages/django/template/response.py" in render
1.             self.content = self.rendered_content

File "/usr/local/lib/python3.4/site-packages/rest_framework/response.py" in rendered_content
1.         ret = renderer.render(self.data, accepted_media_type, context)

File "/usr/local/lib/python3.4/site-packages/rest_framework/renderers.py" in render
1.         context = self.get_context(data, accepted_media_type, renderer_context)

File "/usr/local/lib/python3.4/site-packages/rest_framework/renderers.py" in get_context
1.             'post_form': self.get_rendered_html_form(data, view, 'POST', request),

File "/usr/local/lib/python3.4/site-packages/rest_framework/renderers.py" in get_rendered_html_form
1.             return self.render_form_for_serializer(serializer)

File "/usr/local/lib/python3.4/site-packages/rest_framework/renderers.py" in render_form_for_serializer
1.             {'style': {'template_pack': 'rest_framework/horizontal'}}

File "/usr/local/lib/python3.4/site-packages/rest_framework/renderers.py" in render
1.         return template_render(template, context)

File "/usr/local/lib/python3.4/site-packages/rest_framework/compat.py" in template_render
1.         return template.render(context, request=request)

File "/usr/local/lib/python3.4/site-packages/django/template/backends/django.py" in render
1.             return self.template.render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in render
1.                     return self._render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in _render
1.         return self.nodelist.render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in render
1.                 bit = node.render_annotated(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in render_annotated
1.             return self.render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/defaulttags.py" in render
1.                     nodelist.append(node.render_annotated(context))

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in render_annotated
1.             return self.render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/defaulttags.py" in render
1.                 return nodelist.render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in render
1.                 bit = node.render_annotated(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in render_annotated
1.             return self.render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/library.py" in render
1.         output = self.func(_resolved_args, *_resolved_kwargs)

File "/usr/local/lib/python3.4/site-packages/rest_framework/templatetags/rest_framework.py" in render_field
1.     return renderer.render_field(field, style)

File "/usr/local/lib/python3.4/site-packages/rest_framework/renderers.py" in render_field
1.         return template_render(template, context)

File "/usr/local/lib/python3.4/site-packages/rest_framework/compat.py" in template_render
1.         return template.render(context, request=request)

File "/usr/local/lib/python3.4/site-packages/django/template/backends/django.py" in render
1.             return self.template.render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in render
1.                     return self._render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in _render
1.         return self.nodelist.render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in render
1.                 bit = node.render_annotated(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in render_annotated
1.             return self.render(context)

File "/usr/local/lib/python3.4/site-packages/django/template/defaulttags.py" in render
1.                 values = self.sequence.resolve(context, True)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in resolve
1.                 obj = self.var.resolve(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in resolve
1.             value = self._resolve_lookup(context)

File "/usr/local/lib/python3.4/site-packages/django/template/base.py" in _resolve_lookup
1.                             current = current()

File "/usr/local/lib/python3.4/site-packages/rest_framework/relations.py" in iter_options
1.             self.get_choices(cutoff=self.html_cutoff),

File "/usr/local/lib/python3.4/site-packages/rest_framework/relations.py" in get_choices
1.         return self.child_relation.get_choices(cutoff)

File "/usr/local/lib/python3.4/site-packages/rest_framework/relations.py" in get_choices
1.             for item in queryset

File "/usr/local/lib/python3.4/collections/**init**.py" in **init**
1.         self.__update(_args, *_kwds)

File "/usr/local/lib/python3.4/_collections_abc.py" in update
1.                     self[key] = value

File "/usr/local/lib/python3.4/collections/**init**.py" in **setitem**
1.         if key not in self:

Exception Type: TypeError at /api/users/
Exception Value: unhashable type: 'OrderedDict'

`



Discussion:
The change responsible for this error can be found in `rest_framework/relations.py` line 169/171

https://github.com/tomchristie/django-rest-framework/compare/3.4.3...3.4.4


Resolving patch:
diff --git a/example/settings/dev.py b/example/settings/dev.py
--- a/example/settings/dev.py
+++ b/example/settings/dev.py
@@ -26,6 +26,29 @@
     'example',
 ]
 
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [
+            # insert your TEMPLATE_DIRS here
+        ],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                # Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this
+                # list if you haven't customized them:
+                'django.contrib.auth.context_processors.auth',
+                'django.template.context_processors.debug',
+                'django.template.context_processors.i18n',
+                'django.template.context_processors.media',
+                'django.template.context_processors.static',
+                'django.template.context_processors.tz',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
 STATIC_URL = '/static/'
 
 ROOT_URLCONF = 'example.urls'
diff --git a/rest_framework_json_api/relations.py b/rest_framework_json_api/relations.py
--- a/rest_framework_json_api/relations.py
+++ b/rest_framework_json_api/relations.py
@@ -152,14 +152,16 @@ def to_representation(self, value):
         resource_type = resource_type if resource_type else get_resource_type_from_instance(value)
         return OrderedDict([('type', resource_type), ('id', str(pk))])
 
-    @property
-    def choices(self):
+    def get_choices(self, cutoff=None):
         queryset = self.get_queryset()
         if queryset is None:
             # Ensure that field.choices returns something sensible
             # even when accessed with a read-only field.
             return {}
 
+        if cutoff is not None:
+            queryset = queryset[:cutoff]
+
         return OrderedDict([
             (
                 json.dumps(self.to_representation(item)),


Test patch: