def test_build_json_resource_obj(): resource = {"username": "******", "version": "1.0.0"} serializer = ResourceSerializer(data={"username": "******"}) serializer.is_valid() resource_instance = serializer.save() output = { "type": "user", "id": "1", "attributes": { "username": "******" }, "meta": { "version": "1.0.0" }, } assert (JSONRenderer.build_json_resource_obj( get_serializer_fields(serializer), resource, resource_instance, "user", serializer, ) == output)
def build_serializer_schema(self, serializer, resource_name, SwaggerType, ChildSwaggerType, use_references, is_request=None): fields = json_api_utils.get_serializer_fields(serializer) is_post = self.method.lower() == 'post' id_field = self.extract_id_field(fields, serializer) if id_field is None and not (is_request and is_post): logging.warning( '{view}.{serializer} does not contain id field as every resource should' .format(view=self.view.__class__.__name__, serializer=serializer.__class__.__name__)) attributes, req_attrs = self.extract_attributes( id_field, fields, ChildSwaggerType, use_references, is_request) relationships, req_rels = self.extract_relationships( fields, ChildSwaggerType, use_references, is_request) links = self.extract_links(fields, ChildSwaggerType, use_references) if not is_request else None schema_fields = filter_none( OrderedDict( type=self.build_type_schema(resource_name), id=self.probe_field_inspectors(id_field, ChildSwaggerType, use_references) if id_field and not (self.strip_read_fields_from_request and is_request and is_post) else None, attributes=openapi.Schema(type=openapi.TYPE_OBJECT, properties=attributes, required=req_attrs) if attributes else None, relationships=openapi.Schema(type=openapi.TYPE_OBJECT, properties=relationships, required=req_rels) if relationships else None, links=openapi.Schema(type=openapi.TYPE_OBJECT, properties=links) if links else None)) required_properties = None if self.is_request_or_unknown(is_request): required_properties = filter_none([ 'type', 'id' if 'id' in schema_fields else None, 'attributes' if req_attrs else None, 'relationships' if req_rels else None, ]) return SwaggerType(type=openapi.TYPE_OBJECT, properties=schema_fields, required=required_properties)
def build_json_resource_schema(self, serializer, resource_name, SwaggerType, ChildSwaggerType, use_references, is_request=None): fields = json_api_utils.get_serializer_fields(serializer) id_ = fields.get('id') if id_ is None and self.method.lower() == 'get': logging.warning( '{view}.{serializer} does not contain id field as every resource should' .format(view=self.view.__class__.__name__, serializer=serializer.__class__.__name__)) if id_ is not None and id_.read_only and is_request: id_ = None attributes, req_attributes = self.extract_attributes( fields, ChildSwaggerType, use_references, is_request) relationships, req_relationships = self.extract_relationships( fields, ChildSwaggerType, use_references, is_request) links = self.extract_links(fields, ChildSwaggerType, use_references) if not is_request else None schema_fields = filter_none( OrderedDict( type=SwaggerType(type=openapi.TYPE_STRING, pattern=resource_name), id=self.probe_field_inspectors( id_, ChildSwaggerType, use_references) if id_ else None, attributes=SwaggerType(type=openapi.TYPE_OBJECT, properties=attributes, required=req_attributes) if attributes else None, relationships=SwaggerType(type=openapi.TYPE_OBJECT, properties=relationships, required=req_relationships) if relationships else None, links=SwaggerType(type=openapi.TYPE_OBJECT, properties=links) if links else None)) if self.is_request_or_unknown(is_request): required_properties = ['id', 'type' ] if 'id' in schema_fields else ['type'] else: required_properties = None return SwaggerType(type=openapi.TYPE_OBJECT, properties=schema_fields, required=required_properties)
def test_can_override_methods(): """ Make sure extract_attributes and extract_relationships can be overriden. """ resource = {"username": "******", "version": "1.0.0"} serializer = ResourceSerializer(data={"username": "******"}) serializer.is_valid() resource_instance = serializer.save() output = { "type": "user", "id": "1", "attributes": { "username": "******" }, "meta": { "version": "1.0.0" }, } class CustomRenderer(JSONRenderer): extract_attributes_was_overriden = False extract_relationships_was_overriden = False @classmethod def extract_attributes(cls, fields, resource): cls.extract_attributes_was_overriden = True return super(CustomRenderer, cls).extract_attributes(fields, resource) @classmethod def extract_relationships(cls, fields, resource, resource_instance): cls.extract_relationships_was_overriden = True return super(CustomRenderer, cls).extract_relationships(fields, resource, resource_instance) assert (CustomRenderer.build_json_resource_obj( get_serializer_fields(serializer), resource, resource_instance, "user", serializer, ) == output) assert CustomRenderer.extract_attributes_was_overriden assert CustomRenderer.extract_relationships_was_overriden
def render(self, data, accepted_media_type=None, renderer_context=None): renderer_context = renderer_context or {} view = renderer_context.get("view", None) request = renderer_context.get("request", None) # Get the resource name. resource_name = utils.get_resource_name(renderer_context) # If this is an error response, skip the rest. if resource_name == "errors": return self.render_errors(data, accepted_media_type, renderer_context) # if response.status_code is 204 then the data to be rendered must # be None response = renderer_context.get("response", None) if response is not None and response.status_code == 204: return super().render(None, accepted_media_type, renderer_context) from rest_framework_json_api.views import RelationshipView if isinstance(view, RelationshipView): return self.render_relationship_view(data, accepted_media_type, renderer_context) # If `resource_name` is set to None then render default as the dev # wants to build the output format manually. if resource_name is None or resource_name is False: return super().render(data, accepted_media_type, renderer_context) json_api_data = data # initialize json_api_meta with pagination meta or an empty dict json_api_meta = data.get("meta", {}) if isinstance(data, dict) else {} included_cache = defaultdict(dict) if data and "results" in data: serializer_data = data["results"] else: serializer_data = data serializer = getattr(serializer_data, "serializer", None) included_resources = utils.get_included_resources(request, serializer) if serializer is not None: # Extract root meta for any type of serializer json_api_meta.update( self.extract_root_meta(serializer, serializer_data)) if getattr(serializer, "many", False): json_api_data = list() for position in range(len(serializer_data)): resource = serializer_data[ position] # Get current resource resource_instance = serializer.instance[ position] # Get current instance if isinstance( serializer.child, rest_framework_json_api.serializers. PolymorphicModelSerializer, ): resource_serializer_class = ( serializer.child. get_polymorphic_serializer_for_instance( resource_instance)( context=serializer.child.context)) else: resource_serializer_class = serializer.child fields = utils.get_serializer_fields( resource_serializer_class) force_type_resolution = getattr( resource_serializer_class, "_poly_force_type_resolution", False) json_resource_obj = self.build_json_resource_obj( fields, resource, resource_instance, resource_name, serializer, force_type_resolution, ) json_api_data.append(json_resource_obj) self.extract_included( fields, resource, resource_instance, included_resources, included_cache, ) else: fields = utils.get_serializer_fields(serializer) force_type_resolution = getattr(serializer, "_poly_force_type_resolution", False) resource_instance = serializer.instance json_api_data = self.build_json_resource_obj( fields, serializer_data, resource_instance, resource_name, serializer, force_type_resolution, ) self.extract_included( fields, serializer_data, resource_instance, included_resources, included_cache, ) # Make sure we render data in a specific order render_data = OrderedDict() if isinstance(data, dict) and data.get("links"): render_data["links"] = data.get("links") # format the api root link list if view.__class__ and view.__class__.__name__ == "APIRoot": render_data["data"] = None render_data["links"] = json_api_data else: render_data["data"] = json_api_data if included_cache: if isinstance(json_api_data, list): objects = json_api_data else: objects = [json_api_data] for object in objects: obj_type = object.get("type") obj_id = object.get("id") if obj_type in included_cache and obj_id in included_cache[ obj_type]: del included_cache[obj_type][obj_id] if not included_cache[obj_type]: del included_cache[obj_type] if included_cache: render_data["included"] = list() for included_type in sorted(included_cache.keys()): for included_id in sorted( included_cache[included_type].keys()): render_data["included"].append( included_cache[included_type][included_id]) if json_api_meta: render_data["meta"] = utils.format_field_names(json_api_meta) return super().render(render_data, accepted_media_type, renderer_context)
def extract_included(cls, fields, resource, resource_instance, included_resources, included_cache): """ Adds related data to the top level included key when the request includes ?include=example,example_field2 """ # this function may be called with an empty record (example: Browsable Interface) if not resource_instance: return current_serializer = fields.serializer context = current_serializer.context included_serializers = getattr(current_serializer, "included_serializers", dict()) included_resources = copy.copy(included_resources) included_resources = [ inflection.underscore(value) for value in included_resources ] for field_name, field in iter(fields.items()): # Skip URL field if field_name == api_settings.URL_FIELD_NAME: continue # Skip fields without relations if not utils.is_relationship_field(field): continue try: included_resources.remove(field_name) except ValueError: # Skip fields not in requested included resources # If no child field, directly continue with the next field if field_name not in [ node.split(".")[0] for node in included_resources ]: continue relation_instance = cls.extract_relation_instance( field, resource_instance) if isinstance(relation_instance, Manager): relation_instance = relation_instance.all() serializer_data = resource.get(field_name) if isinstance(field, relations.ManyRelatedField): serializer_class = included_serializers[field_name] field = serializer_class(relation_instance, many=True, context=context) serializer_data = field.data if isinstance(field, relations.RelatedField): if relation_instance is None or not serializer_data: continue many = field._kwargs.get("child_relation", None) is not None if isinstance(field, ResourceRelatedField) and not many: already_included = ( serializer_data["type"] in included_cache and serializer_data["id"] in included_cache[serializer_data["type"]]) if already_included: continue serializer_class = included_serializers[field_name] field = serializer_class(relation_instance, many=many, context=context) serializer_data = field.data new_included_resources = [ key.replace("%s." % field_name, "", 1) for key in included_resources if field_name == key.split(".")[0] ] if isinstance(field, ListSerializer): serializer = field.child relation_type = utils.get_resource_type_from_serializer( serializer) relation_queryset = list(relation_instance) if serializer_data: for position in range(len(serializer_data)): serializer_resource = serializer_data[position] nested_resource_instance = relation_queryset[position] resource_type = (relation_type or utils.get_resource_type_from_instance( nested_resource_instance)) serializer_fields = utils.get_serializer_fields( serializer.__class__(nested_resource_instance, context=serializer.context)) new_item = cls.build_json_resource_obj( serializer_fields, serializer_resource, nested_resource_instance, resource_type, serializer, getattr(serializer, "_poly_force_type_resolution", False), ) included_cache[new_item["type"]][ new_item["id"]] = new_item cls.extract_included( serializer_fields, serializer_resource, nested_resource_instance, new_included_resources, included_cache, ) if isinstance(field, Serializer): relation_type = utils.get_resource_type_from_serializer(field) # Get the serializer fields serializer_fields = utils.get_serializer_fields(field) if serializer_data: new_item = cls.build_json_resource_obj( serializer_fields, serializer_data, relation_instance, relation_type, field, getattr(field, "_poly_force_type_resolution", False), ) included_cache[new_item["type"]][new_item["id"]] = new_item cls.extract_included( serializer_fields, serializer_data, relation_instance, new_included_resources, included_cache, )
def extract_included(cls, fields, resource, resource_instance, included_resources, included_cache): """ Adds related data to the top level included key when the request includes ?include=example,example_field2 """ # this function may be called with an empty record (example: Browsable Interface) if not resource_instance: return current_serializer = fields.serializer context = current_serializer.context included_serializers = utils.get_included_serializers(current_serializer) included_resources = copy.copy(included_resources) included_resources = [inflection.underscore(value) for value in included_resources] for field_name, field in six.iteritems(fields): # Skip URL field if field_name == api_settings.URL_FIELD_NAME: continue # Skip fields without relations or serialized data if not isinstance( field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer) ): continue try: included_resources.remove(field_name) except ValueError: # Skip fields not in requested included resources # If no child field, directly continue with the next field if field_name not in [node.split('.')[0] for node in included_resources]: continue relation_instance = cls.extract_relation_instance( field, resource_instance ) if isinstance(relation_instance, Manager): relation_instance = relation_instance.all() serializer_data = resource.get(field_name) if isinstance(field, relations.ManyRelatedField): serializer_class = included_serializers[field_name] context['parent'] = resource_instance field = serializer_class(relation_instance, many=True, context=context) serializer_data = field.data if isinstance(field, relations.RelatedField): if relation_instance is None or not serializer_data: continue many = field._kwargs.get('child_relation', None) is not None if isinstance(field, ResourceRelatedField) and not many: already_included = serializer_data['type'] in included_cache and \ serializer_data['id'] in included_cache[serializer_data['type']] if already_included: continue serializer_class = included_serializers[field_name] context['parent'] = resource_instance field = serializer_class(relation_instance, many=many, context=context) serializer_data = field.data new_included_resources = [key.replace('%s.' % field_name, '', 1) for key in included_resources if field_name == key.split('.')[0]] if isinstance(field, ListSerializer): serializer = field.child relation_type = utils.get_resource_type_from_serializer(serializer) relation_queryset = list(relation_instance) if serializer_data: for position in range(len(serializer_data)): serializer_resource = serializer_data[position] nested_resource_instance = relation_queryset[position] resource_type = ( relation_type or utils.get_resource_type_from_instance(nested_resource_instance) ) serializer_fields = utils.get_serializer_fields( serializer.__class__( nested_resource_instance, context=serializer.context ) ) new_item = cls.build_json_resource_obj( serializer_fields, serializer_resource, nested_resource_instance, resource_type, getattr(serializer, '_poly_force_type_resolution', False) ) # NEW: Add meta to included resource meta = cls.extract_meta(serializer.__class__, serializer_resource) if meta: new_item.update({'meta': utils._format_object(meta)}) included_cache[new_item['type']][new_item['id']] = \ utils._format_object(new_item) cls.extract_included( serializer_fields, serializer_resource, nested_resource_instance, new_included_resources, included_cache, ) if isinstance(field, Serializer): relation_type = utils.get_resource_type_from_serializer(field) # Get the serializer fields serializer_fields = utils.get_serializer_fields(field) new_item = cls.build_json_resource_obj( serializer_fields, serializer_data, relation_instance, relation_type, getattr(field, '_poly_force_type_resolution', False) ) # NEW: Add meta to included resource meta = cls.extract_meta(serializer_class, serializer_data) if meta: new_item.update({'meta': utils._format_object(meta)}) included_cache[new_item['type']][new_item['id']] = utils._format_object( new_item ) cls.extract_included( serializer_fields, serializer_data, relation_instance, new_included_resources, included_cache, )
def render(self, data, accepted_media_type=None, renderer_context=None): renderer_context = renderer_context or {} view = renderer_context.get("view", None) request = renderer_context.get("request", None) # Get the resource name. resource_name = utils.get_resource_name(renderer_context) # If this is an error response, skip the rest. if resource_name == 'errors': return self.render_errors(data, accepted_media_type, renderer_context) # if response.status_code is 204 then the data to be rendered must # be None response = renderer_context.get('response', None) if response is not None and response.status_code == 204: return super(JSONRenderer, self).render( None, accepted_media_type, renderer_context ) from rest_framework_json_api.views import RelationshipView if isinstance(view, RelationshipView): return self.render_relationship_view(data, accepted_media_type, renderer_context) # If `resource_name` is set to None then render default as the dev # wants to build the output format manually. if resource_name is None or resource_name is False: return super(JSONRenderer, self).render( data, accepted_media_type, renderer_context ) json_api_data = data # initialize json_api_meta with pagination meta or an empty dict json_api_meta = data.get('meta', {}) if isinstance(data, dict) else {} included_cache = defaultdict(dict) if data and 'results' in data: serializer_data = data["results"] else: serializer_data = data serializer = getattr(serializer_data, 'serializer', None) included_resources = utils.get_included_resources(request, serializer) if serializer is not None: # Extract root meta for any type of serializer json_api_meta.update(self.extract_root_meta(serializer, serializer_data)) if getattr(serializer, 'many', False): json_api_data = list() for position in range(len(serializer_data)): resource = serializer_data[position] # Get current resource resource_instance = serializer.instance[position] # Get current instance if isinstance(serializer.child, rest_framework_json_api. serializers.PolymorphicModelSerializer): resource_serializer_class = serializer.child.\ get_polymorphic_serializer_for_instance(resource_instance)() else: resource_serializer_class = serializer.child fields = utils.get_serializer_fields(resource_serializer_class) force_type_resolution = getattr( resource_serializer_class, '_poly_force_type_resolution', False) json_resource_obj = self.build_json_resource_obj( fields, resource, resource_instance, resource_name, force_type_resolution ) meta = self.extract_meta(serializer, resource) if meta: json_resource_obj.update({'meta': utils.format_keys(meta)}) json_api_data.append(json_resource_obj) self.extract_included( fields, resource, resource_instance, included_resources, included_cache ) else: fields = utils.get_serializer_fields(serializer) force_type_resolution = getattr(serializer, '_poly_force_type_resolution', False) resource_instance = serializer.instance json_api_data = self.build_json_resource_obj( fields, serializer_data, resource_instance, resource_name, force_type_resolution ) meta = self.extract_meta(serializer, serializer_data) if meta: json_api_data.update({'meta': utils.format_keys(meta)}) self.extract_included( fields, serializer_data, resource_instance, included_resources, included_cache ) # Make sure we render data in a specific order render_data = OrderedDict() if isinstance(data, dict) and data.get('links'): render_data['links'] = data.get('links') # format the api root link list if view.__class__ and view.__class__.__name__ == 'APIRoot': render_data['data'] = None render_data['links'] = json_api_data else: render_data['data'] = json_api_data if included_cache: render_data['included'] = list() for included_type in sorted(included_cache.keys()): for included_id in sorted(included_cache[included_type].keys()): render_data['included'].append(included_cache[included_type][included_id]) if json_api_meta: render_data['meta'] = utils.format_keys(json_api_meta) return super(JSONRenderer, self).render( render_data, accepted_media_type, renderer_context )
def render(self, data, accepted_media_type=None, renderer_context=None): view = renderer_context.get('view', None) request = renderer_context.get('request', None) # Get the resource name. resource_name = utils.get_resource_name(renderer_context) # If this is an error response, skip the rest. if resource_name == 'errors': return self.render_errors(data, accepted_media_type, renderer_context) from rest_framework_json_api.views import RelationshipView if isinstance(view, RelationshipView): return self.render_relationship_view(data, accepted_media_type, renderer_context) # If `resource_name` is set to None then render default as the dev # wants to build the output format manually. if resource_name is None or resource_name is False: return super().render(data, accepted_media_type, renderer_context) json_api_data = data json_api_included = list() # initialize json_api_meta with pagination meta or an empty dict json_api_meta = data.get('meta', {}) if isinstance(data, dict) else {} if data and 'results' in data: serializer_data = data['results'] else: serializer_data = data serializer = getattr(serializer_data, 'serializer', None) included_resources = utils.get_included_resources(request, serializer) if serializer is not None: # Get the serializer fields fields = utils.get_serializer_fields(serializer) # Extract root meta for any type of serializer json_api_meta.update( self.extract_root_meta(serializer, serializer_data)) if getattr(serializer, 'many', False): json_api_data = list() for position in range(len(serializer_data)): resource = serializer_data[ position] # Get current resource resource_instance = serializer.instance[ position] # Get current instance json_resource_obj = self.build_json_resource_obj( fields, resource, resource_instance, resource_name) meta = self.extract_meta(serializer, resource) if meta: json_resource_obj.update( {'meta': api_utils.format_keys(meta)}) json_api_data.append(json_resource_obj) included = self.extract_included(fields, resource, resource_instance, included_resources) if included: json_api_included.extend(included) else: resource_instance = serializer.instance json_api_data = self.build_json_resource_obj( fields, serializer_data, resource_instance, resource_name) meta = self.extract_meta(serializer, serializer_data) if meta: json_api_data.update({'meta': api_utils.format_keys(meta)}) included = self.extract_included(fields, serializer_data, resource_instance, included_resources) if included: json_api_included.extend(included) # Make sure we render data in a specific order render_data = OrderedDict() if isinstance(data, dict) and data.get('links'): render_data['links'] = data.get('links') from rest_framework.routers import APIRootView # format the api root link list if isinstance(view, APIRootView): render_data['data'] = None render_data['links'] = json_api_data else: render_data['data'] = json_api_data if len(json_api_included) > 0: # Iterate through compound documents to remove duplicates seen = set() unique_compound_documents = list() for included_dict in json_api_included: type_tuple = tuple( (included_dict['type'], included_dict['id'])) if type_tuple not in seen: seen.add(type_tuple) unique_compound_documents.append(included_dict) # Sort the items by type then by id render_data['included'] = sorted(unique_compound_documents, key=lambda item: (item['type'], item['id'])) if json_api_meta: render_data['meta'] = api_utils.format_keys(json_api_meta) return super().render(render_data, accepted_media_type, renderer_context)
def extract_included(cls, fields, resource, resource_instance, included_resources): # this function may be called with an empty record (example: Browsable Interface) if not resource_instance: return included_data = list() current_serializer = fields.serializer context = current_serializer.context included_serializers = utils.get_included_serializers( current_serializer) included_resources = copy.copy(included_resources) included_resources = [ inflection.underscore(value) for value in included_resources ] for field_name, field in six.iteritems(fields): # Skip URL field if field_name == api_settings.URL_FIELD_NAME: continue # Skip fields without relations or serialized data if not isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)): continue try: included_resources.remove(field_name) except ValueError: # Skip fields not in requested included resources # If no child field, directly continue with the next field if field_name not in [ node.split('.')[0] for node in included_resources ]: continue try: relation_instance = getattr(resource_instance, field_name) except AttributeError: try: # For ManyRelatedFields if `related_name` is not set we need to access `foo_set` from `source` relation_instance = getattr(resource_instance, field.child_relation.source) except AttributeError: if not hasattr(current_serializer, field.source): continue serializer_method = getattr(current_serializer, field.source) relation_instance = serializer_method(resource_instance) if isinstance(relation_instance, Manager): relation_instance = relation_instance.all() new_included_resources = [ key.replace('%s.' % field_name, '', 1) for key in included_resources if field_name == key.split('.')[0] ] serializer_data = resource.get(field_name) if isinstance(field, relations.ManyRelatedField): serializer_class = included_serializers[field_name] field = serializer_class(relation_instance, many=True, context=context) serializer_data = field.data if isinstance(field, relations.RelatedField): if relation_instance is None: continue many = field._kwargs.get('child_relation', None) is not None serializer_class = included_serializers[field_name] field = serializer_class(relation_instance, many=many, context=context) serializer_data = field.data if isinstance(field, ListSerializer): serializer = field.child relation_type = utils.get_resource_type_from_serializer( serializer) relation_queryset = list(relation_instance) # Get the serializer fields serializer_fields = utils.get_serializer_fields(serializer) if serializer_data: for position in range(len(serializer_data)): serializer_resource = serializer_data[position] nested_resource_instance = relation_queryset[position] resource_type = (relation_type or utils.get_resource_type_from_instance( nested_resource_instance)) included_data.append( cls.build_json_resource_obj( serializer_fields, serializer_resource, nested_resource_instance, resource_type)) included_data.extend( cls.extract_included(serializer_fields, serializer_resource, nested_resource_instance, new_included_resources)) if isinstance(field, Serializer): relation_type = utils.get_resource_type_from_serializer(field) # Get the serializer fields serializer_fields = utils.get_serializer_fields(field) if serializer_data: included_data.append( cls.build_json_resource_obj(serializer_fields, serializer_data, relation_instance, relation_type)) included_data.extend( cls.extract_included(serializer_fields, serializer_data, relation_instance, new_included_resources)) return api_utils.format_keys(included_data)
def extract_included(cls, request, fields, resource, resource_instance, included_resources): # this function may be called with an empty record (example: Browsable Interface) if not resource_instance: return included_data = list() current_serializer = fields.serializer context = current_serializer.context included_serializers = utils.get_included_serializers( current_serializer) included_resources = copy.copy(included_resources) included_resources = [ inflection.underscore(value) for value in included_resources ] for field_name, field in six.iteritems(fields): # Skip URL field if field_name == api_settings.URL_FIELD_NAME: continue # Skip fields without relations or serialized data if not isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)): continue try: included_resources.remove(field_name) except ValueError: # Skip fields not in requested included resources # If no child field, directly continue with the next field if field_name not in [ node.split('.')[0] for node in included_resources ]: continue try: relation_instance = getattr(resource_instance, field_name) except AttributeError: try: # For ManyRelatedFields if `related_name` is not set we need to access `foo_set` from `source` relation_instance = getattr(resource_instance, field.child_relation.source) except AttributeError: if not hasattr(current_serializer, field.source): continue serializer_method = getattr(current_serializer, field.source) relation_instance = serializer_method(resource_instance) if isinstance(relation_instance, Manager): relation_instance = relation_instance.all() new_included_resources = [ key.replace('%s.' % field_name, '', 1) for key in included_resources if field_name == key.split('.')[0] ] serializer_data = resource.get(field_name) if isinstance(field, RemoteResourceField): user_id = getattr(request.user, 'id', None) roles = request.user.roles pk = serializer_data.get('id') include = ",".join(new_included_resources) try: remote_resource = event_client.get_remote_resource_data( field_name, pk=pk, user_id=user_id, include=include, page_size=1000, roles=roles) body = ujson.loads(remote_resource['body']) if 400 <= remote_resource['status'] < 600: raise RemoteResourceIncludeError( field_name, body["errors"][0]) except RequestTimeout: raise RemoteResourceIncludeTimeoutError(field_name) included_data.append(body['data']) if body.get('included'): included_data.extend(body['included']) # We continue here since RemoteResourceField inherits # form ResourceRelatedField which is a RelatedField continue if isinstance(field, relations.ManyRelatedField): serializer_class = included_serializers[field_name] field = serializer_class(relation_instance, many=True, context=context) serializer_data = field.data if isinstance(field, relations.RelatedField): if relation_instance is None: continue many = field._kwargs.get('child_relation', None) is not None serializer_class = included_serializers[field_name] field = serializer_class(relation_instance, many=many, context=context) serializer_data = field.data if isinstance(field, ListSerializer): serializer = field.child relation_type = utils.get_resource_type_from_serializer( serializer) relation_queryset = list(relation_instance) # Get the serializer fields serializer_fields = utils.get_serializer_fields(serializer) if serializer_data: for position in range(len(serializer_data)): serializer_resource = serializer_data[position] nested_resource_instance = relation_queryset[position] resource_type = (relation_type or utils.get_resource_type_from_instance( nested_resource_instance)) included_data.append( cls.build_json_resource_obj( serializer_fields, serializer_resource, nested_resource_instance, resource_type)) included_data.extend( cls.extract_included(request, serializer_fields, serializer_resource, nested_resource_instance, new_included_resources)) if isinstance(field, Serializer): relation_type = utils.get_resource_type_from_serializer(field) # Get the serializer fields serializer_fields = utils.get_serializer_fields(field) if serializer_data: included_data.append( cls.build_json_resource_obj(serializer_fields, serializer_data, relation_instance, relation_type)) included_data.extend( cls.extract_included(request, serializer_fields, serializer_data, relation_instance, new_included_resources)) return key_formatter()(included_data)
def render(self, data, accepted_media_type=None, renderer_context=None): renderer_context = renderer_context or {} view = renderer_context.get("view", None) request = renderer_context.get("request", None) # Get the resource name. resource_name = utils.get_resource_name(renderer_context) # If this is an error response, skip the rest. if resource_name == 'errors': return self.render_errors(data, accepted_media_type, renderer_context) # if response.status_code is 204 then the data to be rendered must # be None response = renderer_context.get('response', None) if response is not None and response.status_code == 204: return super(JSONRenderer, self).render( None, accepted_media_type, renderer_context ) from rest_framework_json_api.views import RelationshipView if isinstance(view, RelationshipView): return self.render_relationship_view(data, accepted_media_type, renderer_context) # If `resource_name` is set to None then render default as the dev # wants to build the output format manually. if resource_name is None or resource_name is False: return super(JSONRenderer, self).render( data, accepted_media_type, renderer_context ) json_api_data = data # initialize json_api_meta with pagination meta or an empty dict json_api_meta = data.get('meta', {}) if isinstance(data, dict) else {} included_cache = defaultdict(dict) if data and 'results' in data: serializer_data = data["results"] else: serializer_data = data serializer = getattr(serializer_data, 'serializer', None) included_resources = utils.get_included_resources(request, serializer) if serializer is not None: # Extract root meta for any type of serializer json_api_meta.update(self.extract_root_meta(serializer, serializer_data)) if getattr(serializer, 'many', False): json_api_data = list() for position in range(len(serializer_data)): resource = serializer_data[position] # Get current resource resource_instance = serializer.instance[position] # Get current instance if isinstance(serializer.child, rest_framework_json_api. serializers.PolymorphicModelSerializer): resource_serializer_class = serializer.child.\ get_polymorphic_serializer_for_instance(resource_instance)( context=serializer.child.context ) else: resource_serializer_class = serializer.child fields = utils.get_serializer_fields(resource_serializer_class) force_type_resolution = getattr( resource_serializer_class, '_poly_force_type_resolution', False) json_resource_obj = self.build_json_resource_obj( fields, resource, resource_instance, resource_name, force_type_resolution ) meta = self.extract_meta(serializer, resource) if meta: json_resource_obj.update({'meta': utils._format_object(meta)}) json_api_data.append(json_resource_obj) self.extract_included( fields, resource, resource_instance, included_resources, included_cache ) else: fields = utils.get_serializer_fields(serializer) force_type_resolution = getattr(serializer, '_poly_force_type_resolution', False) resource_instance = serializer.instance json_api_data = self.build_json_resource_obj( fields, serializer_data, resource_instance, resource_name, force_type_resolution ) meta = self.extract_meta(serializer, serializer_data) if meta: json_api_data.update({'meta': utils._format_object(meta)}) self.extract_included( fields, serializer_data, resource_instance, included_resources, included_cache ) # Make sure we render data in a specific order render_data = OrderedDict() if isinstance(data, dict) and data.get('links'): render_data['links'] = data.get('links') # format the api root link list if view.__class__ and view.__class__.__name__ == 'APIRoot': render_data['data'] = None render_data['links'] = json_api_data else: render_data['data'] = json_api_data if included_cache: if isinstance(json_api_data, list): objects = json_api_data else: objects = [json_api_data] for object in objects: obj_type = object.get('type') obj_id = object.get('id') if obj_type in included_cache and \ obj_id in included_cache[obj_type]: del included_cache[obj_type][obj_id] if not included_cache[obj_type]: del included_cache[obj_type] if included_cache: render_data['included'] = list() for included_type in sorted(included_cache.keys()): for included_id in sorted(included_cache[included_type].keys()): render_data['included'].append(included_cache[included_type][included_id]) if json_api_meta: render_data['meta'] = utils._format_object(json_api_meta) return super(JSONRenderer, self).render( render_data, accepted_media_type, renderer_context )
def extract_included(cls, fields, resource, resource_instance, included_resources, included_cache): """ Adds related data to the top level included key when the request includes ?include=example,example_field2 """ # this function may be called with an empty record (example: Browsable Interface) if not resource_instance: return current_serializer = fields.serializer context = current_serializer.context included_serializers = utils.get_included_serializers(current_serializer) included_resources = copy.copy(included_resources) included_resources = [inflection.underscore(value) for value in included_resources] for field_name, field in six.iteritems(fields): # Skip URL field if field_name == api_settings.URL_FIELD_NAME: continue # Skip fields without relations or serialized data if not isinstance( field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer) ): continue try: included_resources.remove(field_name) except ValueError: # Skip fields not in requested included resources # If no child field, directly continue with the next field if field_name not in [node.split('.')[0] for node in included_resources]: continue relation_instance = cls.extract_relation_instance( field_name, field, resource_instance, current_serializer ) if isinstance(relation_instance, Manager): relation_instance = relation_instance.all() serializer_data = resource.get(field_name) if isinstance(field, relations.ManyRelatedField): serializer_class = included_serializers[field_name] field = serializer_class(relation_instance, many=True, context=context) serializer_data = field.data if isinstance(field, relations.RelatedField): if relation_instance is None or not serializer_data: continue many = field._kwargs.get('child_relation', None) is not None if isinstance(field, ResourceRelatedField) and not many: already_included = serializer_data['type'] in included_cache and \ serializer_data['id'] in included_cache[serializer_data['type']] if already_included: continue serializer_class = included_serializers[field_name] field = serializer_class(relation_instance, many=many, context=context) serializer_data = field.data new_included_resources = [key.replace('%s.' % field_name, '', 1) for key in included_resources if field_name == key.split('.')[0]] if isinstance(field, ListSerializer): serializer = field.child relation_type = utils.get_resource_type_from_serializer(serializer) relation_queryset = list(relation_instance) if serializer_data: for position in range(len(serializer_data)): serializer_resource = serializer_data[position] nested_resource_instance = relation_queryset[position] resource_type = ( relation_type or utils.get_resource_type_from_instance(nested_resource_instance) ) serializer_fields = utils.get_serializer_fields( serializer.__class__( nested_resource_instance, context=serializer.context ) ) new_item = cls.build_json_resource_obj( serializer_fields, serializer_resource, nested_resource_instance, resource_type, getattr(serializer, '_poly_force_type_resolution', False) ) included_cache[new_item['type']][new_item['id']] = \ utils._format_object(new_item) cls.extract_included( serializer_fields, serializer_resource, nested_resource_instance, new_included_resources, included_cache, ) if isinstance(field, Serializer): relation_type = utils.get_resource_type_from_serializer(field) # Get the serializer fields serializer_fields = utils.get_serializer_fields(field) if serializer_data: new_item = cls.build_json_resource_obj( serializer_fields, serializer_data, relation_instance, relation_type, getattr(field, '_poly_force_type_resolution', False) ) included_cache[new_item['type']][new_item['id']] = utils._format_object( new_item ) cls.extract_included( serializer_fields, serializer_data, relation_instance, new_included_resources, included_cache, )
def render(self, data, accepted_media_type=None, renderer_context=None): view = renderer_context.get("view", None) request = renderer_context.get("request", None) # Get the resource name. resource_name = utils.get_resource_name(renderer_context) # If this is an error response, skip the rest. if resource_name == 'errors': return self.render_errors(data, accepted_media_type, renderer_context) # if response.status_code is 204 then the data to be rendered must # be None response = renderer_context.get('response', None) if response is not None and response.status_code == 204: return super(JSONRenderer, self).render(None, accepted_media_type, renderer_context) from rest_framework_json_api.views import RelationshipView if isinstance(view, RelationshipView): return self.render_relationship_view(data, accepted_media_type, renderer_context) # If `resource_name` is set to None then render default as the dev # wants to build the output format manually. if resource_name is None or resource_name is False: return super(JSONRenderer, self).render(data, accepted_media_type, renderer_context) json_api_data = data json_api_included = list() # initialize json_api_meta with pagination meta or an empty dict json_api_meta = data.get('meta', {}) if isinstance(data, dict) else {} if json_api_meta: meta_pagination = json_api_meta.pop('pagination') meta_links = json_api_meta.pop('links') meta_pagination.update(meta_links) json_api_meta['pagination'] = meta_pagination json_api_meta['include'] = [] json_api_meta['custom'] = [] if data and 'results' in data: serializer_data = data["results"] else: serializer_data = data serializer = getattr(serializer_data, 'serializer', None) included_resources = utils.get_included_resources(request, serializer) if serializer is not None: # Get the serializer fields fields = utils.get_serializer_fields(serializer) # Determine if resource name must be resolved on each instance (polymorphic serializer) force_type_resolution = getattr(serializer, '_poly_force_type_resolution', False) # Extract root meta for any type of serializer json_api_meta.update( self.extract_root_meta(serializer, serializer_data)) if getattr(serializer, 'many', False): json_api_data = list() for position in range(len(serializer_data)): resource = serializer_data[ position] # Get current resource resource_instance = serializer.instance[ position] # Get current instance json_resource_obj = self.build_json_resource_obj( fields, resource, resource_instance, resource_name, force_type_resolution) meta = self.extract_meta(serializer, resource) if meta: json_resource_obj.update( {'meta': utils.format_keys(meta)}) json_api_data.append(json_resource_obj) included = self.extract_included(fields, resource, resource_instance, included_resources) if included: json_api_included.extend(included) else: resource_instance = serializer.instance json_api_data = self.build_json_resource_obj( fields, serializer_data, resource_instance, resource_name, force_type_resolution) meta = self.extract_meta(serializer, serializer_data) if meta: json_api_data.update({'meta': utils.format_keys(meta)}) included = self.extract_included(fields, serializer_data, resource_instance, included_resources) if included: json_api_included.extend(included) # Make sure we render data in a specific order render_data = OrderedDict() if isinstance(data, dict) and data.get('links'): render_data['links'] = data.get('links') # format the api root link list if view.__class__ and view.__class__.__name__ == 'APIRoot': render_data['data'] = None render_data['links'] = json_api_data else: render_data['data'] = json_api_data if len(json_api_included) > 0: # Iterate through compound documents to remove duplicates seen = set() unique_compound_documents = list() for included_dict in json_api_included: type_tuple = tuple( (included_dict['type'], included_dict['id'])) if type_tuple not in seen: seen.add(type_tuple) unique_compound_documents.append(included_dict) # Sort the items by type then by id render_data['included'] = sorted(unique_compound_documents, key=lambda item: (item['type'], item['id'])) if json_api_meta: render_data['meta'] = utils.format_keys(json_api_meta) return super(renderers.JSONRenderer, self).render(render_data, accepted_media_type, renderer_context)