From 0f59d534ce6048de3dacfc2e28481239e747686e Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sat, 22 Mar 2014 21:44:19 -0400 Subject: [PATCH] added non-native valiation only fields usage to the serializer docs --- docs/api-guide/serializers.md | 88 ++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 7ee060af4..321390e5d 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -21,7 +21,7 @@ Let's start by creating a simple object we can use for example purposes: self.email = email self.content = content self.created = created or datetime.datetime.now() - + comment = Comment(email='leila@example.com', content='foo bar') We'll declare a serializer that we can use to serialize and deserialize `Comment` objects. @@ -45,7 +45,7 @@ Declaring a serializer looks very similar to declaring a form: instance.content = attrs.get('content', instance.content) instance.created = attrs.get('created', instance.created) return instance - return Comment(**attrs) + return Comment(**attrs) The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data. @@ -83,8 +83,8 @@ If you need to customize the serialized value of a particular field, you can do These methods are essentially the reverse of `validate_` (see *Validation* below.) ## Deserializing objects - -Deserialization is similar. First we parse a stream into Python native datatypes... + +Deserialization is similar. First we parse a stream into Python native datatypes... from StringIO import StringIO from rest_framework.parsers import JSONParser @@ -174,7 +174,7 @@ To save the deserialized objects created by a serializer, call the `.save()` met The default behavior of the method is to simply call `.save()` on the deserialized object instance. You can override the default save behaviour by overriding the `.save_object(obj)` method on the serializer class. -The generic views provided by REST framework call the `.save()` method when updating or creating entities. +The generic views provided by REST framework call the `.save()` method when updating or creating entities. ## Dealing with nested objects @@ -288,12 +288,12 @@ By default the serializer class will use the `id` key on the incoming data to de slug = serializers.CharField(max_length=100) created = serializers.DateTimeField() ... # Various other fields - + def get_identity(self, data): """ This hook is required for bulk update. We need to override the default, to use the slug as the identity. - + Note that the data has not yet been validated at this point, so we need to deal gracefully with incorrect datatypes. """ @@ -361,7 +361,7 @@ The `depth` option should be set to an integer value that indicates the depth of If you want to customize the way the serialization is done (e.g. using `allow_add_remove`) you'll need to define the field yourself. -## Specifying which fields should be read-only +## Specifying which fields should be read-only You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the `read_only_fields` Meta option, like so: @@ -371,9 +371,9 @@ You may wish to specify multiple fields as read-only. Instead of adding each fi fields = ('id', 'account_name', 'users', 'created') read_only_fields = ('account_name',) -Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option. +Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option. -## Specifying which fields should be write-only +## Specifying which fields should be write-only You may wish to specify multiple fields as write-only. Instead of adding each field explicitly with the `write_only=True` attribute, you may use the `write_only_fields` Meta option, like so: @@ -387,12 +387,12 @@ You may wish to specify multiple fields as write-only. Instead of adding each f """ Instantiate a new User instance. """ - assert instance is None, 'Cannot update users with CreateUserSerializer' + assert instance is None, 'Cannot update users with CreateUserSerializer' user = User(email=attrs['email'], username=attrs['username']) user.set_password(attrs['password']) return user - -## Specifying fields explicitly + +## Specifying fields explicitly You can add extra fields to a `ModelSerializer` or override the default fields by declaring fields on the class, just as you would for a `Serializer` class. @@ -405,6 +405,62 @@ You can add extra fields to a `ModelSerializer` or override the default fields b Extra fields can correspond to any property or callable on the model. +## Specifying non-native validation only fields + +You can add extra non-native `validation only` fields to a `ModelSerializer` provided that you meet the following two conditions: + +**A)** The extra `validation only` fields are removed from the `attrs` parameter prior to invoking the `restore_object()` method on the `parent` serializer class. + +**B)** The extra `validation only` fields are removed from the `fields` attribute prior to invoking the `to_native()` method on the `parent` serializer class during the `object attribute` validations. + +**Note:** You should `not` remove the extra `validation only` fields from the `fields` attribute prior to invoking the `to_native()` method on the `parent` serializer class during the `form creation` process if you are using the built-in browsable API. + + # Example of overriding restore_object() and to_native() attributes + + class UserCreationSerializer(serializers.ModelSerializer): + password_confirmation = serializers.CharField() + accept_our_terms_and_conditions = serializers.BooleanField() + + custom_messages = { + 'password_mismatch': 'The two password fields did not match.', + 'terms_conditions': 'You must accept our terms and conditions.', + } + + def validate_password_confirmation(self, attrs, source): + password_confirmation = attrs[source] + password = attrs['password'] + if password_confirmation != password: + raise serializers.ValidationError(self.custom_messages['password_mismatch']) + return attrs + + def validate_accept_our_terms_and_conditions(self, attrs, source): + accept_our_terms_and_conditions = attrs[source] + if not accept_our_terms_and_conditions: + raise serializers.ValidationError(self.custom_messages['terms_conditions']) + return attrs + + class Meta: + model = ValidationOnlyFieldsExampleModel + fields = ('email', 'password', 'password_confirmation', 'accept_our_terms_and_conditions',) + write_only_fields = ('password',) + validation_only_fields = ('password_confirmation', 'accept_our_terms_and_conditions',) + + def restore_object(self, attrs, instance=None): + # Flow: south-bound -- object creation: model instance + for attr in self.Meta.validation_only_fields: + attrs.pop(attr) + return super(UserCreationSerializer, self).restore_object(attrs, instance) + + def to_native(self, obj): + try: + # Flow: north-bound -- form creation: browser API + return super(UserCreationSerializer, self).to_native(obj) + except AttributeError as e: + # Flow: south-bound -- object validation: model class + for field in self.Meta.validation_only_fields: + self.fields.pop(field) + return super(UserCreationSerializer, self).to_native(obj) + ## Relational fields When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation for `ModelSerializer` is to use the primary keys of the related instances. @@ -514,10 +570,10 @@ For example, if you wanted to be able to set which fields should be used by a se def __init__(self, *args, **kwargs): # Don't pass the 'fields' arg up to the superclass fields = kwargs.pop('fields', None) - + # Instantiate the superclass normally super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs) - + if fields: # Drop any fields that are not specified in the `fields` argument. allowed = set(fields) @@ -540,7 +596,7 @@ This would then allow you to do the following: ## Customising the default fields -The `field_mapping` attribute is a dictionary that maps model classes to serializer classes. Overriding the attribute will let you set a different set of default serializer classes. +The `field_mapping` attribute is a dictionary that maps model classes to serializer classes. Overriding the attribute will let you set a different set of default serializer classes. For more advanced customization than simply changing the default serializer class you can override various `get__field` methods. Doing so will allow you to customize the arguments that each serializer field is initialized with. Each of these methods may either return a field or serializer instance, or `None`.