def test_get_included_serializers(): class IncludedSerializersModel(DJAModel): self = models.ForeignKey("self", on_delete=models.CASCADE) target = models.ForeignKey(ManyToManyTarget, on_delete=models.CASCADE) other_target = models.ForeignKey(ManyToManyTarget, on_delete=models.CASCADE) class Meta: app_label = "tests" class IncludedSerializersSerializer(serializers.ModelSerializer): included_serializers = { "self": "self", "target": ManyToManyTargetSerializer, "other_target": "tests.serializers.ManyToManyTargetSerializer", } class Meta: model = IncludedSerializersModel fields = ("self", "other_target", "target") included_serializers = get_included_serializers(IncludedSerializersSerializer) expected_included_serializers = { "self": IncludedSerializersSerializer, "target": ManyToManyTargetSerializer, "other_target": ManyToManyTargetSerializer, } assert included_serializers == expected_included_serializers
def test_included_serializers(self): serializers = get_included_serializers(DummySerializer) self.assertIn("related", serializers) self.assertEqual( serializers["related"](DummyRelated(pk=42, name="Test")).data, { "id": 42, "name": "Test" }, )
def test_get_included_serializers_against_class(): klass = SerializerWithIncludedSerializers included_serializers = get_included_serializers(klass) expected_included_serializers = { 'blog': BlogSerializer, 'authors': AuthorSerializer, 'comments': CommentSerializer, 'self': klass } assert (six.viewkeys(included_serializers) == six.viewkeys( klass.included_serializers), 'the keys must be preserved') assert included_serializers == expected_included_serializers
def test_get_included_serializers_against_class(): klass = SerializerWithIncludedSerializers included_serializers = get_included_serializers(klass) expected_included_serializers = { 'blog': BlogSerializer, 'authors': AuthorSerializer, 'comments': CommentSerializer, 'self': klass } assert (six.viewkeys(included_serializers) == six.viewkeys(klass.included_serializers), 'the keys must be preserved') assert included_serializers == expected_included_serializers
def test_get_included_serializers_against_instance(): klass = SerializerWithIncludedSerializers instance = klass() included_serializers = get_included_serializers(instance) expected_included_serializers = { 'blog': BlogSerializer, 'authors': AuthorSerializer, 'comments': CommentSerializer, 'self': klass } assert included_serializers.keys() == klass.included_serializers.keys(), ( 'the keys must be preserved') assert included_serializers == expected_included_serializers
def test_substance_fetch_includes(client, admin_user, substance_factory): client.force_authenticate(user=admin_user) model = substance_factory(illdefined=True).instance requested_includes = get_included_serializers(SubstanceSerializer) # Pop common info related fields. for field in ["created_by", "updated_by"]: requested_includes.pop(field) resp = client.get( "/substances/{}".format(model.pk), data={"include": ",".join(requested_includes)}, ) assert resp.status_code == 200 response_included = json.loads(resp.content.decode("utf-8"))["included"] # Assert there is an include resource for every requested include assert len(requested_includes) == len(response_included)
def validate_path(serializer_class, field_path, path): serializers = get_included_serializers(serializer_class) if serializers is None: raise ParseError('This endpoint does not support the include parameter') this_field_name = field_path[0] this_included_serializer = serializers.get(this_field_name) if this_included_serializer is None: raise ParseError( 'This endpoint does not support the include parameter for path {}'.format( path ) ) if len(field_path) > 1: new_included_field_path = field_path[-1:] # We go down one level in the path validate_path(this_included_serializer, new_included_field_path, path)
def validate_path(serializer_class, field_path, path): serializers = get_included_serializers(serializer_class) if serializers is None: raise ParseError( 'This endpoint does not support the include parameter') this_field_name = inflection.underscore(field_path[0]) this_included_serializer = serializers.get(this_field_name) if this_included_serializer is None: raise ParseError( 'This endpoint does not support the include parameter for path {}' .format(path)) if len(field_path) > 1: new_included_field_path = field_path[1:] # We go down one level in the path validate_path(this_included_serializer, new_included_field_path, path)
def to_representation(self, value): if getattr(self, 'pk_field', None) is not None: pk = self.pk_field.to_representation(value.pk) else: pk = value.pk # check to see if this resource has a different resource_name when # included and use that name resource_type = None root = getattr(self.parent, 'parent', self.parent) field_name = self.field_name if self.field_name else self.parent.field_name if getattr(root, 'included_serializers', None) is not None: includes = get_included_serializers(root) if field_name in includes.keys(): resource_type = get_resource_type_from_serializer(includes[field_name]) resource_type = resource_type if resource_type else get_resource_type_from_instance(value) return OrderedDict([('type', resource_type), ('id', str(pk))])
def to_representation(self, value): # force pk to be UUID pk = getattr(value, 'uuid', getattr(value, 'pk')) # check to see if this resource has a different resource_name when # included and use that name resource_type = None root = getattr(self.parent, 'parent', self.parent) field_name = self.field_name if self.field_name else self.parent.field_name if getattr(root, 'included_serializers', None) is not None: includes = get_included_serializers(root) if field_name in includes.keys(): resource_type = get_resource_type_from_serializer( includes[field_name]) resource_type = resource_type if resource_type else get_resource_type_from_instance( value) return OrderedDict([('type', resource_type.lower()), ('id', str(pk))])
def to_representation(self, value): if getattr(self, 'pk_field', None) is not None: pk = self.pk_field.to_representation(value.pk) else: pk = value.pk # check to see if this resource has a different resource_name when # included and use that name resource_type = None root = getattr(self.parent, 'parent', self.parent) field_name = self.field_name if self.field_name else self.parent.field_name if getattr(root, 'included_serializers', None) is not None: includes = get_included_serializers(root) if field_name in includes.keys(): resource_type = get_resource_type_from_serializer( includes[field_name]) resource_type = resource_type if resource_type else get_resource_type_from_instance( value) return OrderedDict([('type', resource_type), ('auction', str(pk))])
def fixture( deterministic_uuids, django_db_reset_sequences, request, viewset, ): """Get fixture and many to many relations of given viewset.""" fixture = request.getfixturevalue(viewset.factory_name) included = get_included_serializers(viewset.serializer_class) for name in sorted(included.keys()): relation_type = getattr(fixture.__class__, name) # pytest factory boy doesn't have native ManyToMany support # so needs to be handled manually if isinstance(relation_type, ManyToManyDescriptor): print("{0}_{1}".format(viewset.factory_name, name)) request.getfixturevalue("{0}_{1}".format(viewset.factory_name, name)) return fixture
def get_resource_type_from_included_serializer(self): """ Check to see it this resource has a different resource_name when included and return that name, or None """ field_name = self.field_name or self.parent.field_name parent = self.get_parent_serializer() if parent is not None: # accept both singular and plural versions of field_name field_names = [ inflection.singularize(field_name), inflection.pluralize(field_name) ] includes = get_included_serializers(parent) for field in field_names: if field in includes.keys(): return get_resource_type_from_serializer(includes[field]) return None
def _get_included_serializers(cls, serializer, prefix="", already_seen=None): if not already_seen: already_seen = set() if serializer in already_seen: return [] included_serializers = [] already_seen.add(serializer) for include, included_serializer in utils.get_included_serializers( serializer).items(): included_serializers.append(f"{prefix}{include}") included_serializers.extend( cls._get_included_serializers( included_serializer, f"{prefix}{include}.", already_seen=already_seen, )) return included_serializers
def test_synonym_serializer_includes(): serializer = get_included_serializers(SynonymSerializer) assert serializer["source"] is SourceSerializer assert serializer["substance"] is SubstanceSerializer assert serializer["synonym_quality"] is SynonymQualitySerializer assert serializer["synonym_type"] is SynonymTypeSerializer
def test_substance_serializer_includes(): serializer = get_included_serializers(SubstanceSerializer) assert serializer["source"] is SourceSerializer assert serializer["substance_type"] is SubstanceTypeSerializer assert serializer["qc_level"] is QCLevelsTypeSerializer assert serializer["associated_compound"] is CompoundSerializer
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 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 ] render_nested_as_attribute = json_api_settings.SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE 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 isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)): continue if isinstance(field, BaseSerializer) and render_nested_as_attribute: 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, getattr(serializer, '_poly_force_type_resolution', False)) included_cache[new_item['type']][new_item['id']] = \ utils.format_field_names(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_field_names(new_item) cls.extract_included( serializer_fields, serializer_data, relation_instance, new_included_resources, included_cache, )
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 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 get_field_info(self, field, field_name): """ Given an instance of a serializer field, return a dictionary of metadata about it. """ field_info = OrderedDict() serializer = field.parent included_serializers = get_included_serializers(serializer) if isinstance(field, serializers.ManyRelatedField): field_info['type'] = self.type_lookup[field.child_relation] else: field_info['type'] = self.type_lookup[field] try: serializer_model = getattr(serializer.Meta, 'model') field_info['relationship_type'] = self.relation_type_lookup[ getattr(serializer_model, field.field_name)] except KeyError: pass except AttributeError: pass else: resource = included_serializers.get(field_name, field) field_info['relationship_resource'] = get_related_resource_type( resource) field_info['required'] = getattr(field, 'required', False) attrs = [ 'read_only', 'write_only', 'label', 'help_text', 'min_length', 'max_length', 'min_value', 'max_value', 'initial' ] for attr in attrs: value = getattr(field, attr, None) if value is not None and value != '': field_info[attr] = force_text(value, strings_only=True) if getattr(field, 'child', None): field_info['child'] = self.get_field_info( field.child, field_name) # TODO: Is `field_name` okay here? elif getattr(field, 'fields', None): field_info['children'] = self.get_serializer_info(field) if (not field_info.get('read_only') and not field_info.get('relationship_resource') and hasattr(field, 'choices')): field_info['choices'] = [{ 'value': choice_value, 'display_name': force_text(choice_name, strings_only=True) } for choice_value, choice_name in field.choices.items()] if hasattr(serializer, 'included_serializers' ) and 'relationship_resource' in field_info: field_info[ 'allows_include'] = field.field_name in serializer.included_serializers return field_info
def test_substance_relationship_serializer_includes(): serializer = get_included_serializers(SubstanceRelationshipSerializer) assert serializer["from_substance"] is SubstanceSerializer assert serializer["to_substance"] is SubstanceSerializer assert serializer["source"] is SourceSerializer assert serializer["relationship_type"] is RelationshipTypeSerializer