diff --git a/rest_framework_json_schema/schema.py b/rest_framework_json_schema/schema.py index 96b22dd..f83a8ee 100644 --- a/rest_framework_json_schema/schema.py +++ b/rest_framework_json_schema/schema.py @@ -119,7 +119,8 @@ def render(self, data, context): def render_attributes(self, data, context): attributes = self.filter_by_fields(self.attributes, context.fields) return OrderedDict( - (self.transformed_names[attr], self.from_data(data, attr)) for attr in attributes + (self.transformed_names[attr], self.from_data(data, attr)) + for attr in attributes if attr in data ) def render_relationships(self, data, context): @@ -132,6 +133,8 @@ def render_relationships(self, data, context): raise IncludeInvalid('Invalid relationship to include: %s' % key) filtered = self.filter_by_fields(self.relationships, context.fields, lambda x: x[0]) + # Filter by missing values in the data + filtered = (rel for rel in filtered if rel[0] in data) for (name, rel) in filtered: relationship, rel_included = self.render_relationship(data, name, rel, context) relationships[self.transformed_names[name]] = relationship @@ -165,7 +168,7 @@ def filter_by_fields(self, names, fields, name_fn=lambda name: name): type_fields = fields[self.type] # This is essentially an intersection, but we preserve the order # of the attributes/relationships specified by the schema. - return [name for name in names if self.transformed_names[name_fn(name)] in type_fields] + return (name for name in names if self.transformed_names[name_fn(name)] in type_fields) class ResourceIdObject(BaseLinkedObject): diff --git a/rest_framework_json_schema/tests/test_schema.py b/rest_framework_json_schema/tests/test_schema.py index 33d1ac6..317fd88 100644 --- a/rest_framework_json_schema/tests/test_schema.py +++ b/rest_framework_json_schema/tests/test_schema.py @@ -227,6 +227,46 @@ class AlbumObject(ResourceObject): ))) self.assertEqual(included, []) + def test_tolerate_missing_attributes(self): + """ + To support write_only data, be tolerant if an attribute isn't in the data. + """ + obj = ResourceObject(id='user_id', type='users', attributes=('first_name', 'last_name')) + primary, included = obj.render({ + 'user_id': '123', + 'first_name': 'John' + }, Context(self.request)) + self.assertEqual(primary, OrderedDict(( + ('id', '123'), + ('type', 'users'), + ('attributes', OrderedDict((('first_name', 'John'),))) + ))) + self.assertEqual(included, []) + + def test_tolerate_missing_relationships(self): + """ + To support write_only data, be tolerant if a relationship isn't in the data. + """ + class AlbumObject(ResourceObject): + type = 'album' + relationships = ('artist', 'artist2') + + # Empty to-one relationship: relationship is 'None' + # Empty to-many relationship: relationship is '[]' + # Single relationship: a ResourceIdObject + # To-Many: an array of ResourceIdObjects + obj = AlbumObject() + + primary, included = obj.render({'id': '123', 'artist2': None}, Context(self.request)) + self.assertEqual(primary, OrderedDict(( + ('id', '123'), + ('type', 'album'), + ('relationships', OrderedDict(( + ('artist2', OrderedDict((('data', None),))), + ))) + ))) + self.assertEqual(included, []) + def test_render_included(self): """ You can render included resources