diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 0cdae1ce3..1f51838a8 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -236,6 +236,8 @@ For example: model = Account exclude = ('id',) +**Note**: All fields names in either `fields` or `exclude` must already be defined in the model or set explicitly, with the exception of `pk` which is a shortcut field to the actual primary key field of the model (by default Django sets this to `id` but can be overidden). + ## Specifiying nested serialization The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `depth` option: diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index ba64f2aa7..bdddc4d80 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -201,7 +201,7 @@ Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer` class SnippetSerializer(serializers.ModelSerializer): class Meta: model = Snippet - fields = ('id', 'title', 'code', 'linenos', 'language', 'style') + fields = ('pk', 'title', 'code', 'linenos', 'language', 'style') diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index f85250bea..ba44edda4 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -59,7 +59,7 @@ Now that we've got some users to work with, we'd better add representations of t class Meta: model = User - fields = ('id', 'username', 'snippets') + fields = ('pk', 'username', 'snippets') Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we've needed to add an explicit field for it. diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4f68ada68..29aa202af 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -130,22 +130,37 @@ class BaseSerializer(Field): # Set up the field field.initialize(parent=self, field_name=key) + pk_field = None + # Add in the default fields fields = self.default_fields(nested) for key, val in fields.items(): if key not in ret: ret[key] = val + try: + # Test if field was marked as pk_field + if getattr(val, 'is_pk_field'): + pk_field = key + except AttributeError: + pass # If 'fields' is specified, use those fields, in that order. if self.opts.fields: new = SortedDict() for key in self.opts.fields: + if key == 'pk': + # 'pk' shortcut: use the primary key found + new[key] = ret[pk_field] + continue new[key] = ret[key] ret = new # Remove anything in 'exclude' if self.opts.exclude: for key in self.opts.exclude: + if key == 'pk': + # 'pk' shortcut: remove the primary key found + ret.pop(pk_field) ret.pop(key, None) return ret @@ -350,12 +365,11 @@ class ModelSerializer(Serializer): fields += [field for field in opts.many_to_many if field.serialize] ret = SortedDict() - is_pk = True # First field in the list is the pk for model_field in fields: - if is_pk: + if model_field.primary_key: + # Django requires models to have only one primary_key so this should be safe field = self.get_pk_field(model_field) - is_pk = False elif model_field.rel and nested: field = self.get_nested_field(model_field) elif model_field.rel: @@ -375,7 +389,10 @@ class ModelSerializer(Serializer): """ Returns a default instance of the pk field. """ - return Field() + field = Field() + # Mark field as primary key + field.is_pk_field = True + return field def get_nested_field(self, model_field): """ diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 8d1de4298..7b4c9f0ec 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -50,7 +50,7 @@ class PersonSerializer(serializers.ModelSerializer): class Meta: model = Person - fields = ('name', 'age', 'info') + fields = ('pk', 'name', 'age', 'info') class BasicTests(TestCase): @@ -112,7 +112,7 @@ class BasicTests(TestCase): """ serializer = PersonSerializer(self.person) self.assertEquals(set(serializer.data.keys()), - set(['name', 'age', 'info'])) + set(['pk', 'name', 'age', 'info'])) def test_field_with_dictionary(self): """ Make sure that dictionaries from fields are left intact @@ -121,6 +121,14 @@ class BasicTests(TestCase): expected = self.person_data self.assertEquals(serializer.data['info'], expected) + def test_pk_field_exists(self): + """ + Verify the `pk` shortcut for primary key (by default: `id`) + """ + + serializer = PersonSerializer(self.person) + self.assertEquals(serializer.data['pk'], self.person.id) + class ValidationTests(TestCase): def setUp(self):