{"action":"created","issue":{"url":"https://api.github.com/repos/encode/django-rest-framework/issues/5521","repository_url":"https://api.github.com/repos/encode/django-rest-framework","labels_url":"https://api.github.com/repos/encode/django-rest-framework/issues/5521/labels{/name}","comments_url":"https://api.github.com/repos/encode/django-rest-framework/issues/5521/comments","events_url":"https://api.github.com/repos/encode/django-rest-framework/issues/5521/events","html_url":"https://github.com/encode/django-rest-framework/issues/5521","id":267410572,"number":5521,"title":"Model's \"clean\" method raises ValidationError with specific fields, but DRF calls them non-field errors","user":{"login":"ataylor32","id":1054222,"avatar_url":"https://avatars0.githubusercontent.com/u/1054222?v=4","gravatar_id":"","url":"https://api.github.com/users/ataylor32","html_url":"https://github.com/ataylor32","followers_url":"https://api.github.com/users/ataylor32/followers","following_url":"https://api.github.com/users/ataylor32/following{/other_user}","gists_url":"https://api.github.com/users/ataylor32/gists{/gist_id}","starred_url":"https://api.github.com/users/ataylor32/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ataylor32/subscriptions","organizations_url":"https://api.github.com/users/ataylor32/orgs","repos_url":"https://api.github.com/users/ataylor32/repos","events_url":"https://api.github.com/users/ataylor32/events{/privacy}","received_events_url":"https://api.github.com/users/ataylor32/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":{"login":"rpkilby","id":2370209,"avatar_url":"https://avatars0.githubusercontent.com/u/2370209?v=4","gravatar_id":"","url":"https://api.github.com/users/rpkilby","html_url":"https://github.com/rpkilby","followers_url":"https://api.github.com/users/rpkilby/followers","following_url":"https://api.github.com/users/rpkilby/following{/other_user}","gists_url":"https://api.github.com/users/rpkilby/gists{/gist_id}","starred_url":"https://api.github.com/users/rpkilby/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rpkilby/subscriptions","organizations_url":"https://api.github.com/users/rpkilby/orgs","repos_url":"https://api.github.com/users/rpkilby/repos","events_url":"https://api.github.com/users/rpkilby/events{/privacy}","received_events_url":"https://api.github.com/users/rpkilby/received_events","type":"User","site_admin":false},"assignees":[{"login":"rpkilby","id":2370209,"avatar_url":"https://avatars0.githubusercontent.com/u/2370209?v=4","gravatar_id":"","url":"https://api.github.com/users/rpkilby","html_url":"https://github.com/rpkilby","followers_url":"https://api.github.com/users/rpkilby/followers","following_url":"https://api.github.com/users/rpkilby/following{/other_user}","gists_url":"https://api.github.com/users/rpkilby/gists{/gist_id}","starred_url":"https://api.github.com/users/rpkilby/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rpkilby/subscriptions","organizations_url":"https://api.github.com/users/rpkilby/orgs","repos_url":"https://api.github.com/users/rpkilby/repos","events_url":"https://api.github.com/users/rpkilby/events{/privacy}","received_events_url":"https://api.github.com/users/rpkilby/received_events","type":"User","site_admin":false}],"milestone":null,"comments":3,"created_at":"2017-10-21T20:12:18Z","updated_at":"2018-03-02T12:18:35Z","closed_at":null,"author_association":"NONE","body":"## Checklist\r\n\r\n- [x] I have verified that that issue exists against the `master` branch of Django REST framework.\r\n- [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.\r\n- [x] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)\r\n- [x] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](http://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)\r\n- [x] I have reduced the issue to the simplest possible case.\r\n- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)\r\n\r\n## Steps to reproduce\r\n\r\n1. Create a fresh Django project\r\n1. Create an app called \"widgets\"\r\n1. Use this code for the \"widgets\" app's \"models.py\" file:\r\n    ```python\r\n    from django.core.exceptions import ValidationError\r\n    from django.db import models\r\n    \r\n    \r\n    class Widget(models.Model):\r\n        STATUS_INCOMPLETE = 'i'\r\n        STATUS_IN_PROGRESS = 'p'\r\n        STATUS_COMPLETE = 'c'\r\n        STATUS_CHOICES = (\r\n            (STATUS_INCOMPLETE, 'Incomplete'),\r\n            (STATUS_IN_PROGRESS, 'In Progress'),\r\n            (STATUS_COMPLETE, 'Complete'),\r\n        )\r\n    \r\n        name = models.CharField(max_length=255, unique=True)\r\n        foo_status = models.CharField(choices=STATUS_CHOICES, max_length=1)\r\n        bar_status = models.CharField(choices=STATUS_CHOICES, max_length=1)\r\n        baz_status = models.CharField(choices=STATUS_CHOICES, max_length=1)\r\n    \r\n        class Meta:\r\n            ordering = ('name',)\r\n    \r\n        def __str__(self):\r\n            return self.name\r\n    \r\n        def clean(self):\r\n            in_progress = 0\r\n            excess_in_progress_status_fields = []\r\n    \r\n            for status_field in (\r\n                'foo_status',\r\n                'bar_status',\r\n                'baz_status',\r\n            ):\r\n                if getattr(self, status_field) == self.STATUS_IN_PROGRESS:\r\n                    in_progress += 1\r\n                    if in_progress > 1:\r\n                        excess_in_progress_status_fields.append(status_field)\r\n    \r\n            if excess_in_progress_status_fields:\r\n                errors = {}\r\n    \r\n                for status_field in excess_in_progress_status_fields:\r\n                    errors[status_field] = (\r\n                        'You cannot have multiple status dropdown menus set to '\r\n                        '\"In Progress\".'\r\n                    )\r\n    \r\n                raise ValidationError(errors)\r\n    ```\r\n1. Use this code for the \"widgets\" app's \"admin.py\" file:\r\n    ```python\r\n    from django.contrib import admin\r\n    \r\n    from .models import Widget\r\n    \r\n    admin.site.register(Widget)\r\n    ```\r\n1. Use this code for the project's \"urls.py\" file:\r\n    ```python\r\n    from django.conf.urls import url, include\r\n    from django.contrib import admin\r\n    from rest_framework import routers, serializers, viewsets\r\n    \r\n    from widgets.models import Widget\r\n    \r\n    \r\n    class WidgetSerializer(serializers.ModelSerializer):\r\n        class Meta:\r\n            model = Widget\r\n            fields = '__all__'\r\n    \r\n        def validate(self, attrs):\r\n            instance = Widget(**attrs)\r\n            instance.clean()\r\n            return attrs\r\n    \r\n    \r\n    class WidgetViewSet(viewsets.ModelViewSet):\r\n        queryset = Widget.objects.all()\r\n        serializer_class = WidgetSerializer\r\n    \r\n    router = routers.DefaultRouter()\r\n    router.register(r'widgets', WidgetViewSet)\r\n    \r\n    urlpatterns = [\r\n        url(r'^', include(router.urls)),\r\n        url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),\r\n        url(r'^admin/', admin.site.urls),\r\n    ]\r\n    ```\r\n1. Try to create a record with all three status fields set to \"In Progress\"\r\n\r\n## Expected behavior\r\n\r\nThe \"bar_status\" and \"baz_status\" fields should each be given an error message. This is how it's handled in the admin:\r\n\r\n![admin](https://user-images.githubusercontent.com/1054222/31855480-3f90dd44-b669-11e7-8ea0-d04f368ad267.png)\r\n\r\n## Actual behavior\r\n\r\nThe two error messages intended for the \"bar_status\" and \"baz_status\" fields are added to \"non_field_errors\" instead:\r\n\r\n![api](https://user-images.githubusercontent.com/1054222/31855481-46dc534e-b669-11e7-88c3-d38e267f49cb.png)"},"comment":{"url":"https://api.github.com/repos/encode/django-rest-framework/issues/comments/369906540","html_url":"https://github.com/encode/django-rest-framework/issues/5521#issuecomment-369906540","issue_url":"https://api.github.com/repos/encode/django-rest-framework/issues/5521","id":369906540,"user":{"login":"jonashaag","id":175722,"avatar_url":"https://avatars1.githubusercontent.com/u/175722?v=4","gravatar_id":"","url":"https://api.github.com/users/jonashaag","html_url":"https://github.com/jonashaag","followers_url":"https://api.github.com/users/jonashaag/followers","following_url":"https://api.github.com/users/jonashaag/following{/other_user}","gists_url":"https://api.github.com/users/jonashaag/gists{/gist_id}","starred_url":"https://api.github.com/users/jonashaag/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonashaag/subscriptions","organizations_url":"https://api.github.com/users/jonashaag/orgs","repos_url":"https://api.github.com/users/jonashaag/repos","events_url":"https://api.github.com/users/jonashaag/events{/privacy}","received_events_url":"https://api.github.com/users/jonashaag/received_events","type":"User","site_admin":false},"created_at":"2018-03-02T12:18:35Z","updated_at":"2018-03-02T12:18:35Z","author_association":"NONE","body":"I just hit this too. I followed the official recommendation for having the validation in `Model.clean` be considered by DRF."}}
{"actor":{"display_login":"jonashaag"}}