def remove_invalid_fields(self, queryset, fields, view, request): valid_fields = [ item[0] for item in self.get_valid_fields(queryset, view, {'request': request}) ] bad_terms = [ term for term in fields if format_value(term.replace(".", "__").lstrip('-'), "underscore") not in valid_fields ] if bad_terms: raise ValidationError('invalid sort parameter{}: {}'.format( ('s' if len(bad_terms) > 1 else ''), ','.join(bad_terms))) # this looks like it duplicates code above, but we want the ValidationError to report # the actual parameter supplied while we want the fields passed to the super() to # be correctly rewritten. # The leading `-` has to be stripped to prevent format_value from turning it into `_`. underscore_fields = [] for item in fields: item_rewritten = item.replace(".", "__") if item_rewritten.startswith('-'): underscore_fields.append( '-' + format_value(item_rewritten.lstrip('-'), "underscore")) else: underscore_fields.append( format_value(item_rewritten, "underscore")) return super(JSONAPIOrderingFilter, self).remove_invalid_fields(queryset, underscore_fields, view, request)
def remove_invalid_fields(self, queryset, fields, view, request): """ Extend :py:meth:`rest_framework.filters.OrderingFilter.remove_invalid_fields` to validate that all provided sort fields exist (as contrasted with the super's behavior which is to silently remove invalid fields). :raises ValidationError: if a sort field is invalid. """ valid_fields = [ item[0] for item in self.get_valid_fields(queryset, view, {'request': request}) ] bad_terms = [ term for term in fields if format_value(term.replace(".", "__").lstrip('-'), "underscore") not in valid_fields ] if bad_terms: raise ValidationError('invalid sort parameter{}: {}'.format( ('s' if len(bad_terms) > 1 else ''), ','.join(bad_terms))) # this looks like it duplicates code above, but we want the ValidationError to report # the actual parameter supplied while we want the fields passed to the super() to # be correctly rewritten. # The leading `-` has to be stripped to prevent format_value from turning it into `_`. underscore_fields = [] for item in fields: item_rewritten = item.replace(".", "__") if item_rewritten.startswith('-'): underscore_fields.append( '-' + format_value(item_rewritten.lstrip('-'), "underscore")) else: underscore_fields.append(format_value(item_rewritten, "underscore")) return super(OrderingFilter, self).remove_invalid_fields( queryset, underscore_fields, view, request)
def to_internal_value(self, data): matched_fields = set(self.alt_structures) & set( self.initial_data.keys()) formatted_fields = [ format_value(f) for f in sorted(self.alt_structures) ] formatted_matched_fields = [ format_value(f) for f in sorted(matched_fields) ] if not matched_fields: raise ValidationError({ "non_field_errors": f"One of {sorted(formatted_fields)} required." }) if len(matched_fields) > 1: raise ValidationError({ "non_field_errors": (f"Only one of {formatted_fields} allowed. " f"Recieved {formatted_matched_fields}.") }) data = super().to_internal_value(data) # calls field validators structure = next(k for k in self.alt_structures if k in data) data["molfile_v3000"] = get_molfile_v3000(data.pop(structure)) if "inchikey" not in data: data["inchikey"] = get_inchikey(data["molfile_v3000"]) return data
def test_get_related_field_name_handles_formatted_link_segments( self, format_links, rf ): # use field name which actually gets formatted related_model_field_name = "related_field_model" class RelatedFieldNameSerializer(serializers.ModelSerializer): related_model_field = ResourceRelatedField(queryset=BasicModel.objects) def __init__(self, *args, **kwargs): self.related_model_field.field_name = related_model_field_name super().__init(*args, **kwargs) class Meta: model = BasicModel class RelatedFieldNameView(ModelViewSet): serializer_class = RelatedFieldNameSerializer url_segment = format_value(related_model_field_name, format_links) request = rf.get(f"/basic_models/1/{url_segment}") view = RelatedFieldNameView() view.setup(request, related_field=url_segment) assert view.get_related_field_name() == related_model_field_name
def json(cls, *args, **kwargs): """Render a JSON POST request.""" serializer = cls.build(*args, **kwargs) attributes = {} relationships = {} for key, value in serializer.initial_data.items(): if is_relation(value): relationships[format_value(key)] = {"data": value} else: attributes[format_value(key)] = value data = {"type": get_resource_type_from_serializer(serializer)} if attributes: data["attributes"] = attributes if relationships: data["relationships"] = relationships return JSONRenderer().render({"data": data})
def get_filterset_kwargs(self, request, queryset, view): """ Turns filter[<field>]=<value> into <field>=<value> which is what DjangoFilterBackend expects :raises ValidationError: for bad filter syntax """ filter_keys = [] # rewrite filter[field] query params to make DjangoFilterBackend work. data = request.query_params.copy() for qp, val in data.items(): m = self.filter_regex.match(qp) if m and (not m.groupdict()['assoc'] or m.groupdict()['ldelim'] != '[' or m.groupdict()['rdelim'] != ']'): raise ValidationError("invalid query parameter: {}".format(qp)) if m and qp != self.search_param: if not val: raise ValidationError("missing {} test value".format(qp)) # convert jsonapi relationship path to Django ORM's __ notation key = m.groupdict()['assoc'].replace('.', '__') # undo JSON_API_FORMAT_FIELD_NAMES conversion: key = format_value(key, 'underscore') data[key] = val filter_keys.append(key) del data[qp] return { 'data': data, 'queryset': queryset, 'request': request, 'filter_keys': filter_keys, }
def exception_handler(exc, context): response = drf_exception_handler(exc, context) if not response: return response errors = [] # handle generic errors. ValidationError('test') in a view for example if isinstance(response.data, list): for message in response.data: errors.append({ 'detail': message, 'source': { 'pointer': '/data', }, 'status': encoding.force_text(response.status_code), }) # handle all errors thrown from serializers else: for field, error in response.data.items(): field = format_value(field) pointer = '/data/attributes/{}'.format(field) # see if they passed a dictionary to ValidationError manually if isinstance(error, dict): errors.append(error) elif isinstance(error, six.string_types): classes = inspect.getmembers(exceptions, inspect.isclass) # DRF sets the `field` to 'detail' for its own exceptions if isinstance(exc, tuple(x[1] for x in classes)): pointer = '/data' errors.append({ 'detail': error, 'source': { 'pointer': pointer, }, 'status': encoding.force_text(response.status_code), }) elif isinstance(error, list): for message in error: errors.append({ 'detail': message, 'source': { 'pointer': pointer, }, 'status': encoding.force_text(response.status_code), }) else: errors.append({ 'detail': error, 'source': { 'pointer': pointer, }, 'status': encoding.force_text(response.status_code), }) context['view'].resource_name = 'errors' response.data = errors return response
def test_get_related_field_name_handles_formatted_link_segments( format_links, rf): url_segment = format_value(related_model_field_name, format_links) request = rf.get(f"/basic_models/1/{url_segment}") view = BasicModelFakeViewSet() view.setup(request, related_field=url_segment) assert view.get_related_field_name() == related_model_field_name
def remove_invalid_fields(self, queryset, fields, view, request): """ Extend :py:meth:`rest_framework.filters.OrderingFilter.remove_invalid_fields` to validate that all provided sort fields exist (as contrasted with the super's behavior which is to silently remove invalid fields). :raises ValidationError: if a sort field is invalid. """ valid_fields = [ item[0] for item in self.get_valid_fields(queryset, view, {"request": request}) ] bad_terms = [ term for term in fields if format_value(term.replace(".", "__").lstrip("-"), "underscore") not in valid_fields ] if bad_terms: raise ValidationError( "invalid sort parameter{}: {}".format( ("s" if len(bad_terms) > 1 else ""), ",".join(bad_terms) ) ) # this looks like it duplicates code above, but we want the ValidationError to report # the actual parameter supplied while we want the fields passed to the super() to # be correctly rewritten. # The leading `-` has to be stripped to prevent format_value from turning it into `_`. underscore_fields = [] for item in fields: item_rewritten = item.replace(".", "__") if item_rewritten.startswith("-"): underscore_fields.append( "-" + format_value(item_rewritten.lstrip("-"), "underscore") ) else: underscore_fields.append(format_value(item_rewritten, "underscore")) return super(OrderingFilter, self).remove_invalid_fields( queryset, underscore_fields, view, request )
def test_parse_formats_field_names( self, settings, format_field_names, parse, ): settings.JSON_API_FORMAT_FIELD_NAMES = format_field_names data = { "data": { "id": "123", "type": "BasicModel", "attributes": { format_value("test_attribute", format_field_names): "test-value" }, "relationships": { format_value("test_relationship", format_field_names): { "data": { "type": "TestRelationship", "id": "123" } } }, } } result = parse(data) assert result == { "id": "123", "test_attribute": "test-value", "test_relationship": { "id": "123", "type": "TestRelationship" }, }
def get_serializer_info(self, serializer): """ Given an instance of a serializer, return a dictionary of metadata about its fields. """ if hasattr(serializer, 'child'): # If this is a `ListSerializer` then we want to examine the # underlying child serializer instance instead. serializer = serializer.child # Remove the URL field if present serializer.fields.pop(api_settings.URL_FIELD_NAME, None) return OrderedDict([ (format_value(field_name), self.get_field_info(field)) for field_name, field in serializer.fields.items() ])
def get_filterset_kwargs(self, request, queryset, view): """ Turns filter[<field>]=<value> into <field>=<value> which is what DjangoFilterBackend expects :raises ValidationError: for bad filter syntax """ filter_keys = [] # rewrite filter[field] query params to make DjangoFilterBackend work. data = request.query_params.copy() for qp, val in request.query_params.lists(): m = self.filter_regex.match(qp) if m and ( not m.groupdict()["assoc"] or m.groupdict()["ldelim"] != "[" or m.groupdict()["rdelim"] != "]" ): raise ValidationError("invalid query parameter: {}".format(qp)) if m and qp != self.search_param: if not all(val): raise ValidationError( "missing value for query parameter {}".format(qp) ) # convert jsonapi relationship path to Django ORM's __ notation key = m.groupdict()["assoc"].replace(".", "__") # undo JSON_API_FORMAT_FIELD_NAMES conversion: key = format_value(key, "underscore") data.setlist(key, val) filter_keys.append(key) del data[qp] return { "data": data, "queryset": queryset, "request": request, "filter_keys": filter_keys, }
def test_format_value_deprecates_default_format_type_argument(): with pytest.deprecated_call(): assert "first_name" == format_value("first_name")
def test_format_value(settings, format_type, output): assert format_value("first_name", format_type) == output
def _format_key(self, s): return format_value(s)
def dispatch(self, request, *args, **kwargs): if "related_field" in kwargs: kwargs["related_field"] = format_value( self.kwargs["related_field"], "underscore") return super().dispatch(request, *args, **kwargs)
def format_string(self, s): return format_value(s)
def get_url(self, name, view_name, kwargs, request): if "related_field" in kwargs: kwargs["related_field"] = format_value(kwargs["related_field"]) return super().get_url(name, view_name, kwargs, request)
def exception_handler(exc, context): # Import this here to avoid potential edge-case circular imports, which # crashes with: # "ImportError: Could not import 'rest_framework_json_api.parsers.JSONParser' for API setting # 'DEFAULT_PARSER_CLASSES'. ImportError: cannot import name 'exceptions'.'" # # Also see: https://github.com/django-json-api/django-rest-framework-json-api/issues/158 from rest_framework.views import exception_handler as drf_exception_handler response = drf_exception_handler(exc, context) if not response: return response errors = [] # handle generic errors. ValidationError('test') in a view for example if isinstance(response.data, list): for message in response.data: errors.append({ 'detail': message, 'source': { 'pointer': '/data', }, 'status': encoding.force_text(response.status_code), }) # handle all errors thrown from serializers else: for field, error in response.data.items(): field = format_value(field) pointer = '/data/attributes/{}'.format(field) # see if they passed a dictionary to ValidationError manually if isinstance(error, dict): errors.append(error) elif isinstance(error, six.string_types): classes = inspect.getmembers(exceptions, inspect.isclass) # DRF sets the `field` to 'detail' for its own exceptions if isinstance(exc, tuple(x[1] for x in classes)): pointer = '/data' errors.append({ 'detail': error, 'source': { 'pointer': pointer, }, 'status': encoding.force_text(response.status_code), }) elif isinstance(error, list): for message in error: errors.append({ 'detail': message, 'source': { 'pointer': pointer, }, 'status': encoding.force_text(response.status_code), }) else: errors.append({ 'detail': error, 'source': { 'pointer': pointer, }, 'status': encoding.force_text(response.status_code), }) context['view'].resource_name = 'errors' response.data = errors return response
def get_related_field_name(self): field_name = self.kwargs["related_field"] return format_value(field_name, "underscore")
def test_format_value(): assert utils.format_value('first_name', 'camelize') == 'firstName' assert utils.format_value('first_name', 'capitalize') == 'FirstName' assert utils.format_value('first_name', 'dasherize') == 'first-name' assert utils.format_value('first-name', 'underscore') == 'first_name'