Repository: encode/django-rest-framework

Issue:
Metaclass attribute depth ignores fields attribute
Hello !
I'm having some trouble using nested serialization with drf 3.0.1:
I found out that the depth argument ignores what is set in the fields argument and if I remove it,
I can't have any nested serialization.
I'm a bit stuck at this point, if I remove it I lose the nested serialization but if I let it, it will put everything it encounters in the nested until throwing errors like `AssertionError: May not set both 'read_only' and 'required'`.
Here's my code :

```
#models.py
class Profile(models.Model):
    user = models.ForeignKey(User)
    name = models.CharField(max_length=80)
    avatar = models.ImageField(storage=protected_media, upload_to=to_user_dir)


class Adventure(models.Model):
    name = models.CharField(max_length=50)
    user_profile = models.ForeignKey(Profile)

#serializers.py
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
    avatar = serializers.ImageField(max_length=None, allow_empty_file=False)

    class Meta:
        model = Profile
        fields = ("url", "avatar", "name") #user field is not present

class NestedAdventureSerializer(serializers.HyperlinkedModelSerializer):
    levels = NestedLevelSerializer(source="level_set"), #some uninteresting serializer
    nested_url = serializers.HyperlinkedIdentityField(view_name='nested_adventure-detail')
    url = serializers.HyperlinkedIdentityField(view_name='adventure-detail')

    class Meta:
        model = Adventure
        fields = ("url", "nested_url", "name", "level_set") #user_profile field is not present
        depth = 2
```

Here's what I get when I print a NestedAdventureSerializer's instance:

```
NestedSerializer(read_only=True, required=True):
    url = HyperlinkedIdentityField(view_name='contenttype-detail')
    name = CharField(max_length=100)
    app_label = CharField(max_length=100, required=True)
    model = CharField(label='Python model class name', max_length=100, required=True)
    class Meta:
        validators = [<UniqueTogetherValidator(queryset=ContentType.objects.all(), fields=(u'app_label', u'model'))>] None
NestedSerializer(read_only=True, required=True):
    url = HyperlinkedIdentityField(view_name='contenttype-detail')
    name = CharField(max_length=100)
    app_label = CharField(max_length=100, required=True)
    model = CharField(label='Python model class name', max_length=100, required=True)
    class Meta:
        validators = [<UniqueTogetherValidator(queryset=ContentType.objects.all(), fields=(u'app_label', u'model'))>] None
NestedSerializer(read_only=True, required=True):
    url = HyperlinkedIdentityField(view_name='adventure-detail')
    name = CharField(max_length=50)
    user_profile = NestedSerializer(read_only=True):
        url = HyperlinkedIdentityField(view_name='profile-detail')
        name = CharField(max_length=80)
        avatar = ImageField(max_length=100)
        user = NestedSerializer(read_only=True):
             # and it goes on with django's User model until throwing assertions
NestedAdventureSerializer():
    url = HyperlinkedIdentityField(view_name='adventure-detail')
    nested_url = HyperlinkedIdentityField(view_name='nested_adventure-detail')
    name = CharField(max_length=50)
    level_set = ListSerializer(child=NestedSerializer(read_only=True):, read_only=True)
```

Thank you in advance for your help !



Discussion:
Hi @PhilipGarnero, thanks for raising the issue.

I'm going to be slightly unhelpful at this point and close the issue because it's not sufficiently broken down into an easily actionable ticket - there's simply too much stuff there that's specific to your application and I can't really see what I'm supposed to be looking at.

What I'd suggest is that you break this down to the _simplest_ possible case that you can find that demonstrates the issue your seeing. (Eg. a couple of models each with one or two fields.) That process will help nail down if it's actually a bug requiring action, or a usage issue.

Once you've got a simple reviewable example I'd be very happy to reopen the ticket and give it further consideration.

Apologies for not being more help at this point, but we do need to see tickets presented in a way that makes it easy for us to assess if they are valid issues or not.

Thanks for your understanding.


Resolving patch:
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -327,7 +327,9 @@ def get_validators(self):
         Returns a list of validator callables.
         """
         # Used by the lazily-evaluated `validators` property.
-        return getattr(getattr(self, 'Meta', None), 'validators', [])
+        meta = getattr(self, 'Meta', None)
+        validators = getattr(meta, 'validators', None)
+        return validators[:] if validators else []
 
     def get_initial(self):
         if hasattr(self, 'initial_data'):
@@ -696,7 +698,7 @@ class ModelSerializer(Serializer):
     you need you should either declare the extra/differing fields explicitly on
     the serializer class, or simply use a `Serializer` class.
     """
-    _field_mapping = ClassLookupDict({
+    serializer_field_mapping = {
         models.AutoField: IntegerField,
         models.BigIntegerField: IntegerField,
         models.BooleanField: BooleanField,
@@ -719,8 +721,10 @@ class ModelSerializer(Serializer):
         models.TextField: CharField,
         models.TimeField: TimeField,
         models.URLField: URLField,
-    })
-    _related_class = PrimaryKeyRelatedField
+    }
+    serializer_related_class = PrimaryKeyRelatedField
+
+    # Default `create` and `update` behavior...
 
     def create(self, validated_data):
         """
@@ -791,69 +795,81 @@ def update(self, instance, validated_data):
 
         return instance
 
-    def get_validators(self):
-        # If the validators have been declared explicitly then use that.
-        validators = getattr(getattr(self, 'Meta', None), 'validators', None)
-        if validators is not None:
-            return validators
+    # Determine the fields to apply...
 
-        # Determine the default set of validators.
-        validators = []
-        model_class = self.Meta.model
-        field_names = set([
-            field.source for field in self.fields.values()
-            if (field.source != '*') and ('.' not in field.source)
-        ])
+    def get_fields(self):
+        """
+        Return the dict of field names -> field instances that should be
+        used for `self.fields` when instantiating the serializer.
+        """
+        assert hasattr(self, 'Meta'), (
+            'Class {serializer_class} missing "Meta" attribute'.format(
+                serializer_class=self.__class__.__name__
+            )
+        )
+        assert hasattr(self.Meta, 'model'), (
+            'Class {serializer_class} missing "Meta.model" attribute'.format(
+                serializer_class=self.__class__.__name__
+            )
+        )
 
-        # Note that we make sure to check `unique_together` both on the
-        # base model class, but also on any parent classes.
-        for parent_class in [model_class] + list(model_class._meta.parents.keys()):
-            for unique_together in parent_class._meta.unique_together:
-                if field_names.issuperset(set(unique_together)):
-                    validator = UniqueTogetherValidator(
-                        queryset=parent_class._default_manager,
-                        fields=unique_together
-                    )
-                    validators.append(validator)
+        declared_fields = copy.deepcopy(self._declared_fields)
+        model = getattr(self.Meta, 'model')
+        depth = getattr(self.Meta, 'depth', 0)
 
-        # Add any unique_for_date/unique_for_month/unique_for_year constraints.
-        info = model_meta.get_field_info(model_class)
-        for field_name, field in info.fields_and_pk.items():
-            if field.unique_for_date and field_name in field_names:
-                validator = UniqueForDateValidator(
-                    queryset=model_class._default_manager,
-                    field=field_name,
-                    date_field=field.unique_for_date
-                )
-                validators.append(validator)
+        if depth is not None:
+            assert depth >= 0, "'depth' may not be negative."
+            assert depth <= 10, "'depth' may not be greater than 10."
 
-            if field.unique_for_month and field_name in field_names:
-                validator = UniqueForMonthValidator(
-                    queryset=model_class._default_manager,
-                    field=field_name,
-                    date_field=field.unique_for_month
-                )
-                validators.append(validator)
+        # Retrieve metadata about fields & relationships on the model class.
+        info = model_meta.get_field_info(model)
+        field_names = self.get_field_names(declared_fields, info)
 
-            if field.unique_for_year and field_name in field_names:
-                validator = UniqueForYearValidator(
-                    queryset=model_class._default_manager,
-                    field=field_name,
-                    date_field=field.unique_for_year
-                )
-                validators.append(validator)
+        # Determine any extra field arguments and hidden fields that
+        # should be included
+        extra_kwargs = self.get_extra_kwargs()
+        extra_kwargs, hidden_fields = self.get_uniqueness_extra_kwargs(
+            field_names, declared_fields, extra_kwargs
+        )
 
-        return validators
+        # Determine the fields that should be included on the serializer.
+        fields = OrderedDict()
 
-    def get_fields(self):
-        declared_fields = copy.deepcopy(self._declared_fields)
+        for field_name in field_names:
+            # If the field is explicitly declared on the class then use that.
+            if field_name in declared_fields:
+                fields[field_name] = declared_fields[field_name]
+                continue
 
-        ret = OrderedDict()
-        model = getattr(self.Meta, 'model')
+            # Determine the serializer field class and keyword arguments.
+            field_class, field_kwargs = self.build_field(
+                field_name, info, model, depth
+            )
+
+            # Include any kwargs defined in `Meta.extra_kwargs`
+            field_kwargs = self.build_field_kwargs(
+                field_kwargs, extra_kwargs, field_name
+            )
+
+            # Create the serializer field.
+            fields[field_name] = field_class(**field_kwargs)
+
+        # Add in any hidden fields.
+        fields.update(hidden_fields)
+
+        return fields
+
+    # Methods for determining the set of field names to include...
+
+    def get_field_names(self, declared_fields, info):
+        """
+        Returns the list of all field names that should be created when
+        instantiating this serializer class. This is based on the default
+        set of fields, but also takes into account the `Meta.fields` or
+        `Meta.exclude` options if they have been specified.
+        """
         fields = getattr(self.Meta, 'fields', None)
         exclude = getattr(self.Meta, 'exclude', None)
-        depth = getattr(self.Meta, 'depth', 0)
-        extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
 
         if fields and not isinstance(fields, (list, tuple)):
             raise TypeError(
@@ -867,192 +883,191 @@ def get_fields(self):
                 type(exclude).__name__
             )
 
-        assert not (fields and exclude), "Cannot set both 'fields' and 'exclude'."
+        assert not (fields and exclude), (
+            "Cannot set both 'fields' and 'exclude' options on "
+            "serializer {serializer_class}.".format(
+                serializer_class=self.__class__.__name__
+            )
+        )
 
-        extra_kwargs = self._include_additional_options(extra_kwargs)
+        if fields is not None:
+            # Ensure that all declared fields have also been included in the
+            # `Meta.fields` option.
+            for field_name in declared_fields:
+                assert field_name in fields, (
+                    "The field '{field_name}' was declared on serializer "
+                    "{serializer_class}, but has not been included in the "
+                    "'fields' option.".format(
+                        field_name=field_name,
+                        serializer_class=self.__class__.__name__
+                    )
+                )
+            return fields
+
+        # Use the default set of field names if `Meta.fields` is not specified.
+        fields = self.get_default_field_names(declared_fields, info)
+
+        if exclude is not None:
+            # If `Meta.exclude` is included, then remove those fields.
+            for field_name in exclude:
+                assert field_name in fields, (
+                    "The field '{field_name}' was include on serializer "
+                    "{serializer_class} in the 'exclude' option, but does "
+                    "not match any model field.".format(
+                        field_name=field_name,
+                        serializer_class=self.__class__.__name__
+                    )
+                )
+                fields.remove(field_name)
 
-        # Retrieve metadata about fields & relationships on the model class.
-        info = model_meta.get_field_info(model)
+        return fields
 
-        # Use the default set of field names if none is supplied explicitly.
-        if fields is None:
-            fields = self._get_default_field_names(declared_fields, info)
-            exclude = getattr(self.Meta, 'exclude', None)
-            if exclude is not None:
-                for field_name in exclude:
-                    assert field_name in fields, (
-                        'The field in the `exclude` option must be a model field. Got %s.' %
-                        field_name
-                    )
-                    fields.remove(field_name)
+    def get_default_field_names(self, declared_fields, model_info):
+        """
+        Return the default list of field names that will be used if the
+        `Meta.fields` option is not specified.
+        """
+        return (
+            [model_info.pk.name] +
+            list(declared_fields.keys()) +
+            list(model_info.fields.keys()) +
+            list(model_info.forward_relations.keys())
+        )
 
-        # Determine the set of model fields, and the fields that they map to.
-        # We actually only need this to deal with the slightly awkward case
-        # of supporting `unique_for_date`/`unique_for_month`/`unique_for_year`.
-        model_field_mapping = {}
-        for field_name in fields:
-            if field_name in declared_fields:
-                field = declared_fields[field_name]
-                source = field.source or field_name
+    # Methods for constructing serializer fields...
+
+    def build_field(self, field_name, info, model_class, nested_depth):
+        """
+        Return a two tuple of (cls, kwargs) to build a serializer field with.
+        """
+        if field_name in info.fields_and_pk:
+            model_field = info.fields_and_pk[field_name]
+            return self.build_standard_field(field_name, model_field)
+
+        elif field_name in info.relations:
+            relation_info = info.relations[field_name]
+            if not nested_depth:
+                return self.build_relational_field(field_name, relation_info)
             else:
-                try:
-                    source = extra_kwargs[field_name]['source']
-                except KeyError:
-                    source = field_name
-            # Model fields will always have a simple source mapping,
-            # they can't be nested attribute lookups.
-            if '.' not in source and source != '*':
-                model_field_mapping[source] = field_name
+                return self.build_nested_field(field_name, relation_info, nested_depth)
 
-        # Determine if we need any additional `HiddenField` or extra keyword
-        # arguments to deal with `unique_for` dates that are required to
-        # be in the input data in order to validate it.
-        hidden_fields = {}
-        unique_constraint_names = set()
+        elif hasattr(model_class, field_name):
+            return self.build_property_field(field_name, model_class)
 
-        for model_field_name, field_name in model_field_mapping.items():
-            try:
-                model_field = model._meta.get_field(model_field_name)
-            except FieldDoesNotExist:
-                continue
+        elif field_name == api_settings.URL_FIELD_NAME:
+            return self.build_url_field(field_name, model_class)
 
-            # Include each of the `unique_for_*` field names.
-            unique_constraint_names |= set([
-                model_field.unique_for_date,
-                model_field.unique_for_month,
-                model_field.unique_for_year
-            ])
+        return self.build_unknown_field(field_name, model_class)
 
-        unique_constraint_names -= set([None])
+    def build_standard_field(self, field_name, model_field):
+        """
+        Create regular model fields.
+        """
+        field_mapping = ClassLookupDict(self.serializer_field_mapping)
+
+        field_class = field_mapping[model_field]
+        field_kwargs = get_field_kwargs(field_name, model_field)
+
+        if 'choices' in field_kwargs:
+            # Fields with choices get coerced into `ChoiceField`
+            # instead of using their regular typed field.
+            field_class = ChoiceField
+        if not issubclass(field_class, ModelField):
+            # `model_field` is only valid for the fallback case of
+            # `ModelField`, which is used when no other typed field
+            # matched to the model field.
+            field_kwargs.pop('model_field', None)
+        if not issubclass(field_class, CharField) and not issubclass(field_class, ChoiceField):
+            # `allow_blank` is only valid for textual fields.
+            field_kwargs.pop('allow_blank', None)
+
+        return field_class, field_kwargs
+
+    def build_relational_field(self, field_name, relation_info):
+        """
+        Create fields for forward and reverse relationships.
+        """
+        field_class = self.serializer_related_class
+        field_kwargs = get_relation_kwargs(field_name, relation_info)
 
-        # Include each of the `unique_together` field names,
-        # so long as all the field names are included on the serializer.
-        for parent_class in [model] + list(model._meta.parents.keys()):
-            for unique_together_list in parent_class._meta.unique_together:
-                if set(fields).issuperset(set(unique_together_list)):
-                    unique_constraint_names |= set(unique_together_list)
+        # `view_name` is only valid for hyperlinked relationships.
+        if not issubclass(field_class, HyperlinkedRelatedField):
+            field_kwargs.pop('view_name', None)
 
-        # Now we have all the field names that have uniqueness constraints
-        # applied, we can add the extra 'required=...' or 'default=...'
-        # arguments that are appropriate to these fields, or add a `HiddenField` for it.
-        for unique_constraint_name in unique_constraint_names:
-            # Get the model field that is referred too.
-            unique_constraint_field = model._meta.get_field(unique_constraint_name)
+        return field_class, field_kwargs
 
-            if getattr(unique_constraint_field, 'auto_now_add', None):
-                default = CreateOnlyDefault(timezone.now)
-            elif getattr(unique_constraint_field, 'auto_now', None):
-                default = timezone.now
-            elif unique_constraint_field.has_default():
-                default = unique_constraint_field.default
-            else:
-                default = empty
+    def build_nested_field(self, field_name, relation_info, nested_depth):
+        """
+        Create nested fields for forward and reverse relationships.
+        """
+        class NestedSerializer(ModelSerializer):
+            class Meta:
+                model = relation_info.related_model
+                depth = nested_depth
 
-            if unique_constraint_name in model_field_mapping:
-                # The corresponding field is present in the serializer
-                if unique_constraint_name not in extra_kwargs:
-                    extra_kwargs[unique_constraint_name] = {}
-                if default is empty:
-                    if 'required' not in extra_kwargs[unique_constraint_name]:
-                        extra_kwargs[unique_constraint_name]['required'] = True
-                else:
-                    if 'default' not in extra_kwargs[unique_constraint_name]:
-                        extra_kwargs[unique_constraint_name]['default'] = default
-            elif default is not empty:
-                # The corresponding field is not present in the,
-                # serializer. We have a default to use for it, so
-                # add in a hidden field that populates it.
-                hidden_fields[unique_constraint_name] = HiddenField(default=default)
+        field_class = NestedSerializer
+        field_kwargs = get_nested_relation_kwargs(relation_info)
 
-        # Now determine the fields that should be included on the serializer.
-        for field_name in fields:
-            if field_name in declared_fields:
-                # Field is explicitly declared on the class, use that.
-                ret[field_name] = declared_fields[field_name]
-                continue
+        return field_class, field_kwargs
 
-            elif field_name in info.fields_and_pk:
-                # Create regular model fields.
-                model_field = info.fields_and_pk[field_name]
-                field_cls = self._field_mapping[model_field]
-                kwargs = get_field_kwargs(field_name, model_field)
-                if 'choices' in kwargs:
-                    # Fields with choices get coerced into `ChoiceField`
-                    # instead of using their regular typed field.
-                    field_cls = ChoiceField
-                if not issubclass(field_cls, ModelField):
-                    # `model_field` is only valid for the fallback case of
-                    # `ModelField`, which is used when no other typed field
-                    # matched to the model field.
-                    kwargs.pop('model_field', None)
-                if not issubclass(field_cls, CharField) and not issubclass(field_cls, ChoiceField):
-                    # `allow_blank` is only valid for textual fields.
-                    kwargs.pop('allow_blank', None)
-
-            elif field_name in info.relations:
-                # Create forward and reverse relationships.
-                relation_info = info.relations[field_name]
-                if depth:
-                    field_cls = self._get_nested_class(depth, relation_info)
-                    kwargs = get_nested_relation_kwargs(relation_info)
-                else:
-                    field_cls = self._related_class
-                    kwargs = get_relation_kwargs(field_name, relation_info)
-                    # `view_name` is only valid for hyperlinked relationships.
-                    if not issubclass(field_cls, HyperlinkedRelatedField):
-                        kwargs.pop('view_name', None)
-
-            elif hasattr(model, field_name):
-                # Create a read only field for model methods and properties.
-                field_cls = ReadOnlyField
-                kwargs = {}
-
-            elif field_name == api_settings.URL_FIELD_NAME:
-                # Create the URL field.
-                field_cls = HyperlinkedIdentityField
-                kwargs = get_url_kwargs(model)
+    def build_property_field(self, field_name, model_class):
+        """
+        Create a read only field for model methods and properties.
+        """
+        field_class = ReadOnlyField
+        field_kwargs = {}
 
-            else:
-                raise ImproperlyConfigured(
-                    'Field name `%s` is not valid for model `%s`.' %
-                    (field_name, model.__class__.__name__)
-                )
+        return field_class, field_kwargs
 
-            # Check that any fields declared on the class are
-            # also explicitly included in `Meta.fields`.
-            missing_fields = set(declared_fields.keys()) - set(fields)
-            if missing_fields:
-                missing_field = list(missing_fields)[0]
-                raise ImproperlyConfigured(
-                    'Field `%s` has been declared on serializer `%s`, but '
-                    'is missing from `Meta.fields`.' %
-                    (missing_field, self.__class__.__name__)
-                )
+    def build_url_field(self, field_name, model_class):
+        """
+        Create a field representing the object's own URL.
+        """
+        field_class = HyperlinkedIdentityField
+        field_kwargs = get_url_kwargs(model_class)
 
-            # Populate any kwargs defined in `Meta.extra_kwargs`
-            extras = extra_kwargs.get(field_name, {})
-            if extras.get('read_only', False):
-                for attr in [
-                    'required', 'default', 'allow_blank', 'allow_null',
-                    'min_length', 'max_length', 'min_value', 'max_value',
-                    'validators', 'queryset'
-                ]:
-                    kwargs.pop(attr, None)
+        return field_class, field_kwargs
 
-            if extras.get('default') and kwargs.get('required') is False:
-                kwargs.pop('required')
+    def build_unknown_field(self, field_name, model_class):
+        """
+        Raise an error on any unknown fields.
+        """
+        raise ImproperlyConfigured(
+            'Field name `%s` is not valid for model `%s`.' %
+            (field_name, model_class.__name__)
+        )
+
+    def build_field_kwargs(self, kwargs, extra_kwargs, field_name):
+        """
+        Include an 'extra_kwargs' that have been included for this field,
+        possibly removing any incompatible existing keyword arguments.
+        """
+        extras = extra_kwargs.get(field_name, {})
 
-            kwargs.update(extras)
+        if extras.get('read_only', False):
+            for attr in [
+                'required', 'default', 'allow_blank', 'allow_null',
+                'min_length', 'max_length', 'min_value', 'max_value',
+                'validators', 'queryset'
+            ]:
+                kwargs.pop(attr, None)
 
-            # Create the serializer field.
-            ret[field_name] = field_cls(**kwargs)
+        if extras.get('default') and kwargs.get('required') is False:
+            kwargs.pop('required')
 
-        for field_name, field in hidden_fields.items():
-            ret[field_name] = field
+        kwargs.update(extras)
 
-        return ret
+        return kwargs
+
+    # Methods for determining additional keyword arguments to apply...
+
+    def get_extra_kwargs(self):
+        """
+        Return a dictionary mapping field names to a dictionary of
+        additional keyword arguments.
+        """
+        extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
 
-    def _include_additional_options(self, extra_kwargs):
         read_only_fields = getattr(self.Meta, 'read_only_fields', None)
         if read_only_fields is not None:
             for field_name in read_only_fields:
@@ -1100,21 +1115,202 @@ def _include_additional_options(self, extra_kwargs):
 
         return extra_kwargs
 
-    def _get_default_field_names(self, declared_fields, model_info):
+    def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):
+        """
+        Return any additional field options that need to be included as a
+        result of uniqueness constraints on the model. This is returned as
+        a two-tuple of:
+
+        ('dict of updated extra kwargs', 'mapping of hidden fields')
+        """
+        model = getattr(self.Meta, 'model')
+        model_fields = self._get_model_fields(
+            field_names, declared_fields, extra_kwargs
+        )
+
+        # Determine if we need any additional `HiddenField` or extra keyword
+        # arguments to deal with `unique_for` dates that are required to
+        # be in the input data in order to validate it.
+        unique_constraint_names = set()
+
+        for model_field in model_fields.values():
+            # Include each of the `unique_for_*` field names.
+            unique_constraint_names |= set([
+                model_field.unique_for_date,
+                model_field.unique_for_month,
+                model_field.unique_for_year
+            ])
+
+        unique_constraint_names -= set([None])
+
+        # Include each of the `unique_together` field names,
+        # so long as all the field names are included on the serializer.
+        for parent_class in [model] + list(model._meta.parents.keys()):
+            for unique_together_list in parent_class._meta.unique_together:
+                if set(field_names).issuperset(set(unique_together_list)):
+                    unique_constraint_names |= set(unique_together_list)
+
+        # Now we have all the field names that have uniqueness constraints
+        # applied, we can add the extra 'required=...' or 'default=...'
+        # arguments that are appropriate to these fields, or add a `HiddenField` for it.
+        hidden_fields = {}
+        uniqueness_extra_kwargs = {}
+
+        for unique_constraint_name in unique_constraint_names:
+            # Get the model field that is referred too.
+            unique_constraint_field = model._meta.get_field(unique_constraint_name)
+
+            if getattr(unique_constraint_field, 'auto_now_add', None):
+                default = CreateOnlyDefault(timezone.now)
+            elif getattr(unique_constraint_field, 'auto_now', None):
+                default = timezone.now
+            elif unique_constraint_field.has_default():
+                default = unique_constraint_field.default
+            else:
+                default = empty
+
+            if unique_constraint_name in model_fields:
+                # The corresponding field is present in the serializer
+                if default is empty:
+                    uniqueness_extra_kwargs[unique_constraint_name] = {'required': True}
+                else:
+                    uniqueness_extra_kwargs[unique_constraint_name] = {'default': default}
+            elif default is not empty:
+                # The corresponding field is not present in the,
+                # serializer. We have a default to use for it, so
+                # add in a hidden field that populates it.
+                hidden_fields[unique_constraint_name] = HiddenField(default=default)
+
+        # Update `extra_kwargs` with any new options.
+        for key, value in uniqueness_extra_kwargs.items():
+            if key in extra_kwargs:
+                extra_kwargs[key].update(value)
+            else:
+                extra_kwargs[key] = value
+
+        return extra_kwargs, hidden_fields
+
+    def _get_model_fields(self, field_names, declared_fields, extra_kwargs):
+        """
+        Returns all the model fields that are being mapped to by fields
+        on the serializer class.
+        Returned as a dict of 'model field name' -> 'model field'.
+        Used internally by `get_uniqueness_field_options`.
+        """
+        model = getattr(self.Meta, 'model')
+        model_fields = {}
+
+        for field_name in field_names:
+            if field_name in declared_fields:
+                # If the field is declared on the serializer
+                field = declared_fields[field_name]
+                source = field.source or field_name
+            else:
+                try:
+                    source = extra_kwargs[field_name]['source']
+                except KeyError:
+                    source = field_name
+
+            if '.' in source or source == '*':
+                # Model fields will always have a simple source mapping,
+                # they can't be nested attribute lookups.
+                continue
+
+            try:
+                model_fields[source] = model._meta.get_field(source)
+            except FieldDoesNotExist:
+                pass
+
+        return model_fields
+
+    # Determine the validators to apply...
+
+    def get_validators(self):
+        """
+        Determine the set of validators to use when instantiating serializer.
+        """
+        # If the validators have been declared explicitly then use that.
+        validators = getattr(getattr(self, 'Meta', None), 'validators', None)
+        if validators is not None:
+            return validators[:]
+
+        # Otherwise use the default set of validators.
         return (
-            [model_info.pk.name] +
-            list(declared_fields.keys()) +
-            list(model_info.fields.keys()) +
-            list(model_info.forward_relations.keys())
+            self.get_unique_together_validators() +
+            self.get_unique_for_date_validators()
         )
 
-    def _get_nested_class(self, nested_depth, relation_info):
-        class NestedSerializer(ModelSerializer):
-            class Meta:
-                model = relation_info.related
-                depth = nested_depth
+    def get_unique_together_validators(self):
+        """
+        Determine a default set of validators for any unique_together contraints.
+        """
+        model_class_inheritance_tree = (
+            [self.Meta.model] +
+            list(self.Meta.model._meta.parents.keys())
+        )
 
-        return NestedSerializer
+        # The field names we're passing though here only include fields
+        # which may map onto a model field. Any dotted field name lookups
+        # cannot map to a field, and must be a traversal, so we're not
+        # including those.
+        field_names = set([
+            field.source for field in self.fields.values()
+            if (field.source != '*') and ('.' not in field.source)
+        ])
+
+        # Note that we make sure to check `unique_together` both on the
+        # base model class, but also on any parent classes.
+        validators = []
+        for parent_class in model_class_inheritance_tree:
+            for unique_together in parent_class._meta.unique_together:
+                if field_names.issuperset(set(unique_together)):
+                    validator = UniqueTogetherValidator(
+                        queryset=parent_class._default_manager,
+                        fields=unique_together
+                    )
+                    validators.append(validator)
+        return validators
+
+    def get_unique_for_date_validators(self):
+        """
+        Determine a default set of validators for the following contraints:
+
+        * unique_for_date
+        * unique_for_month
+        * unique_for_year
+        """
+        info = model_meta.get_field_info(self.Meta.model)
+        default_manager = self.Meta.model._default_manager
+        field_names = [field.source for field in self.fields.values()]
+
+        validators = []
+
+        for field_name, field in info.fields_and_pk.items():
+            if field.unique_for_date and field_name in field_names:
+                validator = UniqueForDateValidator(
+                    queryset=default_manager,
+                    field=field_name,
+                    date_field=field.unique_for_date
+                )
+                validators.append(validator)
+
+            if field.unique_for_month and field_name in field_names:
+                validator = UniqueForMonthValidator(
+                    queryset=default_manager,
+                    field=field_name,
+                    date_field=field.unique_for_month
+                )
+                validators.append(validator)
+
+            if field.unique_for_year and field_name in field_names:
+                validator = UniqueForYearValidator(
+                    queryset=default_manager,
+                    field=field_name,
+                    date_field=field.unique_for_year
+                )
+                validators.append(validator)
+
+        return validators
 
 
 class HyperlinkedModelSerializer(ModelSerializer):
@@ -1125,9 +1321,13 @@ class HyperlinkedModelSerializer(ModelSerializer):
     * A 'url' field is included instead of the 'id' field.
     * Relationships to other instances are hyperlinks, instead of primary keys.
     """
-    _related_class = HyperlinkedRelatedField
+    serializer_related_class = HyperlinkedRelatedField
 
-    def _get_default_field_names(self, declared_fields, model_info):
+    def get_default_field_names(self, declared_fields, model_info):
+        """
+        Return the default list of field names that will be used if the
+        `Meta.fields` option is not specified.
+        """
         return (
             [api_settings.URL_FIELD_NAME] +
             list(declared_fields.keys()) +
@@ -1135,10 +1335,16 @@ def _get_default_field_names(self, declared_fields, model_info):
             list(model_info.forward_relations.keys())
         )
 
-    def _get_nested_class(self, nested_depth, relation_info):
+    def build_nested_field(self, field_name, relation_info, nested_depth):
+        """
+        Create nested fields for forward and reverse relationships.
+        """
         class NestedSerializer(HyperlinkedModelSerializer):
             class Meta:
-                model = relation_info.related
-                depth = nested_depth
+                model = relation_info.related_model
+                depth = nested_depth - 1
+
+        field_class = NestedSerializer
+        field_kwargs = get_nested_relation_kwargs(relation_info)
 
-        return NestedSerializer
+        return field_class, field_kwargs
diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py
--- a/rest_framework/utils/model_meta.py
+++ b/rest_framework/utils/model_meta.py
@@ -24,7 +24,7 @@
 
 RelationInfo = namedtuple('RelationInfo', [
     'model_field',
-    'related',
+    'related_model',
     'to_many',
     'has_through_model'
 ])
@@ -77,7 +77,7 @@ def get_field_info(model):
     for field in [field for field in opts.fields if field.serialize and field.rel]:
         forward_relations[field.name] = RelationInfo(
             model_field=field,
-            related=_resolve_model(field.rel.to),
+            related_model=_resolve_model(field.rel.to),
             to_many=False,
             has_through_model=False
         )
@@ -86,7 +86,7 @@ def get_field_info(model):
     for field in [field for field in opts.many_to_many if field.serialize]:
         forward_relations[field.name] = RelationInfo(
             model_field=field,
-            related=_resolve_model(field.rel.to),
+            related_model=_resolve_model(field.rel.to),
             to_many=True,
             has_through_model=(
                 not field.rel.through._meta.auto_created
@@ -99,7 +99,7 @@ def get_field_info(model):
         accessor_name = relation.get_accessor_name()
         reverse_relations[accessor_name] = RelationInfo(
             model_field=None,
-            related=relation.model,
+            related_model=relation.model,
             to_many=relation.field.rel.multiple,
             has_through_model=False
         )
@@ -109,7 +109,7 @@ def get_field_info(model):
         accessor_name = relation.get_accessor_name()
         reverse_relations[accessor_name] = RelationInfo(
             model_field=None,
-            related=relation.model,
+            related_model=relation.model,
             to_many=True,
             has_through_model=(
                 (getattr(relation.field.rel, 'through', None) is not None)


Test patch:
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -206,7 +206,7 @@ class Meta:
 
         with self.assertRaises(ImproperlyConfigured) as excinfo:
             TestSerializer().fields
-        expected = 'Field name `invalid` is not valid for model `ModelBase`.'
+        expected = 'Field name `invalid` is not valid for model `RegularFieldsModel`.'
         assert str(excinfo.exception) == expected
 
     def test_missing_field(self):
@@ -221,11 +221,11 @@ class Meta:
                 model = RegularFieldsModel
                 fields = ('auto_field',)
 
-        with self.assertRaises(ImproperlyConfigured) as excinfo:
+        with self.assertRaises(AssertionError) as excinfo:
             TestSerializer().fields
         expected = (
-            'Field `missing` has been declared on serializer '
-            '`TestSerializer`, but is missing from `Meta.fields`.'
+            "The field 'missing' was declared on serializer TestSerializer, "
+            "but has not been included in the 'fields' option."
         )
         assert str(excinfo.exception) == expected
 
@@ -607,5 +607,5 @@ class Meta:
         exception = result.exception
         self.assertEqual(
             str(exception),
-            "Cannot set both 'fields' and 'exclude'."
+            "Cannot set both 'fields' and 'exclude' options on serializer ExampleSerializer."
         )