A Django REST framework API adapter for the JSON:API spec.
—
JSON:API compliant error formatting, field name formatting utilities, resource type management, and settings configuration for customizing JSON:API behavior.
Main exception handler that converts Django REST framework errors to JSON:API format.
def exception_handler(exc, context):
"""
JSON:API compliant exception handler.
Converts Django REST framework exceptions to JSON:API error format
with proper error objects containing status, detail, and source information.
Args:
exc: Exception instance
context: Exception context with view and request information
Returns:
Response: JSON:API formatted error response or None
"""Usage example:
# settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler',
}
# Validation errors become:
# {
# "errors": [{
# "status": "400",
# "detail": "This field is required.",
# "source": {"pointer": "/data/attributes/title"}
# }]
# }
# Permission errors become:
# {
# "errors": [{
# "status": "403",
# "detail": "You do not have permission to perform this action."
# }]
# }Utility function to check if a view uses JSON:API renderer.
def rendered_with_json_api(view):
"""
Check if view is rendered with JSON:API renderer.
Args:
view: View instance
Returns:
bool: True if view uses JSONRenderer
"""HTTP 409 Conflict exception for JSON:API responses.
class Conflict(exceptions.APIException):
"""
HTTP 409 Conflict exception.
Used for resource conflicts in JSON:API applications.
"""
status_code = 409
default_detail = "Conflict."Usage example:
from rest_framework_json_api.exceptions import Conflict
def update_article(self, instance, validated_data):
if instance.locked:
raise Conflict("Article is locked and cannot be modified.")
return super().update(instance, validated_data)def get_resource_name(context, expand_polymorphic_types=False):
"""
Get resource name from view context.
Args:
context: Serializer context with view and request
expand_polymorphic_types: Whether to expand polymorphic types
Returns:
str or list: Resource name or list of polymorphic types
"""
def get_resource_type_from_model(model):
"""
Get resource type from Django model.
Args:
model: Django model class
Returns:
str: Resource type name
"""
def get_resource_type_from_serializer(serializer):
"""
Get resource type from serializer.
Args:
serializer: Serializer instance or class
Returns:
str: Resource type name
"""
def get_resource_type_from_instance(instance):
"""
Get resource type from model instance.
Args:
instance: Django model instance
Returns:
str: Resource type name
"""
def get_resource_type_from_queryset(queryset):
"""
Get resource type from QuerySet.
Args:
queryset: Django QuerySet
Returns:
str: Resource type name
"""def format_field_names(obj, format_type=None):
"""
Format all field names in an object.
Args:
obj: Dict or object with field names to format
format_type: Format type override
Returns:
dict: Object with formatted field names
"""
def format_field_name(value, format_type=None):
"""
Format a single field name.
Args:
value: Field name to format
format_type: Format type override
Returns:
str: Formatted field name
"""
def undo_format_field_names(obj):
"""
Reverse field name formatting.
Args:
obj: Dict with formatted field names
Returns:
dict: Object with original field names
"""
def undo_format_field_name(value):
"""
Reverse format a single field name.
Args:
value: Formatted field name
Returns:
str: Original field name
"""Usage example:
# With JSON_API_FORMAT_FIELD_NAMES = True
format_field_name('created_at') # Returns 'created-at'
format_field_name('author_name') # Returns 'author-name'
undo_format_field_name('created-at') # Returns 'created_at'
undo_format_field_name('author-name') # Returns 'author_name'def get_serializer_fields(serializer):
"""
Get fields from serializer, handling both regular and list serializers.
Args:
serializer: Serializer instance
Returns:
dict or None: Serializer fields
"""
def get_included_resources(request, serializer_class):
"""
Parse include parameter from request.
Args:
request: HTTP request object
serializer_class: Serializer class
Returns:
list: List of include paths
"""
def get_resource_id(resource_instance):
"""
Get resource ID from instance.
Args:
resource_instance: Model instance
Returns:
str: Resource ID
"""def format_errors(data):
"""
Format errors in JSON:API format.
Args:
data: Error data to format
Returns:
dict: JSON:API formatted errors
"""
def format_drf_errors(response, context, exc):
"""
Format Django REST framework errors as JSON:API errors.
Args:
response: DRF error response
context: Exception context
exc: Exception instance
Returns:
Response: JSON:API formatted error response
"""class Hyperlink:
"""
Hyperlink representation for JSON:API links.
Represents hyperlinks in JSON:API responses with href and meta.
"""
def __init__(self, href, meta=None):
"""
Initialize hyperlink.
Args:
href: Link URL
meta: Optional link metadata
"""
self.href = href
self.meta = metaJSON:API metadata implementation for OPTIONS requests.
class JSONAPIMetadata(SimpleMetadata):
"""
JSON:API metadata implementation for OPTIONS responses.
Provides field information and relationship metadata
in a JSON:API compatible format.
"""
type_lookup = {
# Field type mappings for metadata
}
relation_type_lookup = {
# Relationship type mappings
}
def determine_metadata(self, request, view):
"""
Generate metadata for view.
Args:
request: HTTP request
view: View instance
Returns:
dict: Metadata information
"""Settings management class for JSON:API configuration.
class JSONAPISettings:
"""
Settings object for JSON:API configuration.
Allows JSON:API settings to be accessed as properties
with fallback to defaults.
"""
def __init__(self, user_settings=None, defaults=None):
"""
Initialize settings.
Args:
user_settings: User-defined settings
defaults: Default settings
"""
def __getattr__(self, attr):
"""
Get setting value with fallback to default.
Args:
attr: Setting name
Returns:
Setting value
Raises:
AttributeError: If setting is invalid
"""json_api_settings = JSONAPISettings()
"""Global JSON:API settings instance."""
def reload_json_api_settings(*args, **kwargs):
"""
Reload JSON:API settings when Django settings change.
Connected to Django's setting_changed signal.
"""Available JSON:API settings:
# settings.py
JSON_API_FORMAT_FIELD_NAMES = True # Convert snake_case to kebab-case
JSON_API_FORMAT_TYPES = True # Format resource type names
JSON_API_FORMAT_RELATED_LINKS = True # Format related link segments
JSON_API_PLURALIZE_TYPES = True # Pluralize resource type names
JSON_API_UNIFORM_EXCEPTIONS = True # Use JSON:API errors everywhere# When True:
# Model field: created_at -> JSON:API: "created-at"
# Model field: author_name -> JSON:API: "author-name"
# When False:
# Model field: created_at -> JSON:API: "created_at"
# Model field: author_name -> JSON:API: "author_name"# When True:
# Model: BlogPost -> Resource type: "blog-posts"
# Model: UserProfile -> Resource type: "user-profiles"
# When False:
# Model: BlogPost -> Resource type: "BlogPost"
# Model: UserProfile -> Resource type: "UserProfile"# When True:
# Model: Article -> Resource type: "articles"
# Model: Category -> Resource type: "categories"
# When False:
# Model: Article -> Resource type: "article"
# Model: Category -> Resource type: "category"# When True: All views use JSON:API error format
# When False: Only JSON:API views use JSON:API error formatJSON:API error responses follow this structure:
{
"errors": [
{
"id": "unique-error-id", # Optional
"status": "400", # HTTP status code
"code": "validation_error", # Application-specific error code
"title": "Validation Error", # Human-readable title
"detail": "This field is required.", # Detailed error message
"source": { # Error source information
"pointer": "/data/attributes/title", # JSON Pointer to error location
"parameter": "filter[invalid]" # Query parameter that caused error
},
"meta": { # Additional metadata
"field": "title"
}
}
]
}# Field validation error
{
"errors": [{
"status": "400",
"detail": "This field is required.",
"source": {"pointer": "/data/attributes/title"}
}]
}
# Relationship validation error
{
"errors": [{
"status": "400",
"detail": "Invalid pk \"999\" - object does not exist.",
"source": {"pointer": "/data/relationships/author"}
}]
}# Authentication required
{
"errors": [{
"status": "401",
"detail": "Authentication credentials were not provided."
}]
}
# Permission denied
{
"errors": [{
"status": "403",
"detail": "You do not have permission to perform this action."
}]
}{
"errors": [{
"status": "404",
"detail": "Not found."
}]
}from rest_framework_json_api.exceptions import (
exception_handler,
rendered_with_json_api,
Conflict
)
from rest_framework_json_api.utils import (
get_resource_name,
get_resource_type_from_model,
get_resource_type_from_serializer,
get_resource_type_from_instance,
format_field_names,
format_field_name,
undo_format_field_names,
undo_format_field_name,
get_serializer_fields,
get_included_resources,
format_errors,
format_drf_errors,
Hyperlink
)
from rest_framework_json_api.metadata import JSONAPIMetadata
from rest_framework_json_api.settings import (
JSONAPISettings,
json_api_settings,
reload_json_api_settings
)Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework-jsonapi