Skip to content

Model's "clean" method raises ValidationError with specific fields, but DRF calls them non-field errors #5521

@ataylor32

Description

@ataylor32

Checklist

  • I have verified that that issue exists against the master branch of Django REST framework.
  • I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
  • This is not a usage question. (Those should be directed to the discussion group instead.)
  • This cannot be dealt with as a third party library. (We prefer new functionality to be in the form of third party libraries where possible.)
  • I have reduced the issue to the simplest possible case.
  • I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

Steps to reproduce

  1. Create a fresh Django project
  2. Create an app called "widgets"
  3. Use this code for the "widgets" app's "models.py" file:
    from django.core.exceptions import ValidationError
    from django.db import models
    
    
    class Widget(models.Model):
        STATUS_INCOMPLETE = 'i'
        STATUS_IN_PROGRESS = 'p'
        STATUS_COMPLETE = 'c'
        STATUS_CHOICES = (
            (STATUS_INCOMPLETE, 'Incomplete'),
            (STATUS_IN_PROGRESS, 'In Progress'),
            (STATUS_COMPLETE, 'Complete'),
        )
    
        name = models.CharField(max_length=255, unique=True)
        foo_status = models.CharField(choices=STATUS_CHOICES, max_length=1)
        bar_status = models.CharField(choices=STATUS_CHOICES, max_length=1)
        baz_status = models.CharField(choices=STATUS_CHOICES, max_length=1)
    
        class Meta:
            ordering = ('name',)
    
        def __str__(self):
            return self.name
    
        def clean(self):
            in_progress = 0
            excess_in_progress_status_fields = []
    
            for status_field in (
                'foo_status',
                'bar_status',
                'baz_status',
            ):
                if getattr(self, status_field) == self.STATUS_IN_PROGRESS:
                    in_progress += 1
                    if in_progress > 1:
                        excess_in_progress_status_fields.append(status_field)
    
            if excess_in_progress_status_fields:
                errors = {}
    
                for status_field in excess_in_progress_status_fields:
                    errors[status_field] = (
                        'You cannot have multiple status dropdown menus set to '
                        '"In Progress".'
                    )
    
                raise ValidationError(errors)
  4. Use this code for the "widgets" app's "admin.py" file:
    from django.contrib import admin
    
    from .models import Widget
    
    admin.site.register(Widget)
  5. Use this code for the project's "urls.py" file:
    from django.conf.urls import url, include
    from django.contrib import admin
    from rest_framework import routers, serializers, viewsets
    
    from widgets.models import Widget
    
    
    class WidgetSerializer(serializers.ModelSerializer):
        class Meta:
            model = Widget
            fields = '__all__'
    
        def validate(self, attrs):
            instance = Widget(**attrs)
            instance.clean()
            return attrs
    
    
    class WidgetViewSet(viewsets.ModelViewSet):
        queryset = Widget.objects.all()
        serializer_class = WidgetSerializer
    
    router = routers.DefaultRouter()
    router.register(r'widgets', WidgetViewSet)
    
    urlpatterns = [
        url(r'^', include(router.urls)),
        url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
        url(r'^admin/', admin.site.urls),
    ]
  6. Try to create a record with all three status fields set to "In Progress"

Expected behavior

The "bar_status" and "baz_status" fields should each be given an error message. This is how it's handled in the admin:

admin

Actual behavior

The two error messages intended for the "bar_status" and "baz_status" fields are added to "non_field_errors" instead:

api

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions