Dynamic global and instance settings for your django project
—
Django REST Framework integration providing serializers, viewsets, and permissions for API-based preference management. This enables programmatic access to preferences through a comprehensive REST API.
Serializers for converting preference objects to/from JSON representations with proper validation and type handling.
class PreferenceValueField(serializers.Field):
"""
Custom DRF field for handling preference value serialization/deserialization.
Automatically handles type conversion based on the preference type
and validates values according to preference validation rules.
"""
def to_representation(self, value):
"""Convert preference value to API representation."""
def to_internal_value(self, data):
"""Convert API data to preference value."""
class PreferenceSerializer(serializers.Serializer):
"""
Base serializer for preference objects.
Fields:
- section (CharField, read-only): Preference section
- name (CharField, read-only): Preference name
- identifier (SerializerMethodField): Section__name identifier
- default (SerializerMethodField): Default value
- value (PreferenceValueField): Current preference value
- verbose_name (SerializerMethodField): Display name
- help_text (SerializerMethodField): Help text
- additional_data (SerializerMethodField): Additional metadata
- field (SerializerMethodField): Form field configuration
"""
section = serializers.CharField(read_only=True)
name = serializers.CharField(read_only=True)
identifier = serializers.SerializerMethodField()
default = serializers.SerializerMethodField()
value = PreferenceValueField()
verbose_name = serializers.SerializerMethodField()
help_text = serializers.SerializerMethodField()
additional_data = serializers.SerializerMethodField()
field = serializers.SerializerMethodField()
def get_identifier(self, obj) -> str:
"""Return section__name identifier."""
def get_default(self, obj):
"""Return default value."""
def get_verbose_name(self, obj) -> str:
"""Return verbose name."""
def get_help_text(self, obj) -> str:
"""Return help text."""
def get_additional_data(self, obj) -> dict:
"""Return additional preference metadata."""
def get_field(self, obj) -> dict:
"""Return form field configuration."""
def validate_value(self, value):
"""
Validate preference value using form field validation.
Args:
- value: Value to validate
Returns:
Validated value
Raises:
- ValidationError: If value is invalid
"""
def update(self, instance, validated_data):
"""
Update preference value and send signal.
Args:
- instance: Preference instance
- validated_data: Validated data
Returns:
Updated preference instance
"""
class GlobalPreferenceSerializer(PreferenceSerializer):
"""
Serializer for global preferences.
Inherits all functionality from PreferenceSerializer
with global preference specific configuration.
"""ViewSets providing API endpoints for preference CRUD operations with proper permissions and bulk operations.
class PreferenceViewSet(
mixins.UpdateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet
):
"""
Base API viewset for preference CRUD operations.
Provides:
- GET /preferences/ - List all preferences
- GET /preferences/{identifier}/ - Retrieve specific preference
- PUT/PATCH /preferences/{identifier}/ - Update preference value
- POST /preferences/bulk/ - Bulk update multiple preferences
"""
serializer_class = PreferenceSerializer
lookup_field = 'identifier'
def get_queryset(self):
"""Initialize and filter preferences."""
def get_manager(self) -> PreferencesManager:
"""Return preference manager for this viewset."""
def init_preferences(self):
"""Ensure preferences are populated in database."""
def get_object(self):
"""
Get preference by identifier (section__name format).
Returns:
Preference instance
Raises:
- Http404: If preference not found
"""
def get_section_and_name(self, identifier: str) -> tuple:
"""
Parse preference identifier into section and name.
Args:
- identifier: Preference identifier (section__name)
Returns:
Tuple of (section, name)
"""
@action(detail=False, methods=['post'])
def bulk(self, request):
"""
Bulk update multiple preferences atomically.
Request Body:
{
"preferences": {
"section__name": value,
"section__name2": value2,
...
}
}
Returns:
Updated preferences data
"""
class GlobalPreferencesViewSet(PreferenceViewSet):
"""
API viewset for global preferences.
Endpoints:
- GET /global-preferences/ - List global preferences
- GET /global-preferences/{identifier}/ - Get specific preference
- PUT /global-preferences/{identifier}/ - Update preference
- POST /global-preferences/bulk/ - Bulk update
"""
queryset = GlobalPreferenceModel.objects.all()
serializer_class = GlobalPreferenceSerializer
permission_classes = [GlobalPreferencePermission]
class PerInstancePreferenceViewSet(PreferenceViewSet):
"""
Base viewset for per-instance preferences.
Subclasses must implement get_related_instance() method
to return the instance for preference filtering.
"""
def get_manager(self, **kwargs) -> PreferencesManager:
"""Return manager with instance context."""
def get_queryset(self):
"""Filter preferences by related instance."""
def get_related_instance(self):
"""
Abstract method to get the related instance.
Must be implemented by subclasses.
Returns:
Related model instance
"""Permission classes for controlling access to preference API endpoints.
class GlobalPreferencePermission(DjangoModelPermissions):
"""
Permission class for global preferences API.
Controls access to global preference endpoints based on
Django model permissions for GlobalPreferenceModel.
"""
def has_permission(self, request, view):
"""Check if user has permission for the action."""
def has_object_permission(self, request, view, obj):
"""Check if user has permission for specific preference."""import requests
from django.conf import settings
# Base API URL
api_base = f"{settings.BASE_URL}/api/preferences/"
# List all global preferences
response = requests.get(f"{api_base}global-preferences/")
preferences = response.json()
# Get specific preference
response = requests.get(f"{api_base}global-preferences/general__title/")
preference = response.json()
print(preference['value']) # Current value
print(preference['default']) # Default value
# Update preference
response = requests.patch(
f"{api_base}global-preferences/general__title/",
json={'value': 'New Site Title'},
headers={'Content-Type': 'application/json'}
)
# Bulk update multiple preferences
response = requests.post(
f"{api_base}global-preferences/bulk/",
json={
'preferences': {
'general__title': 'New Title',
'general__maintenance_mode': True,
'ui__theme': 'dark'
}
}
)// Fetch preferences
async function getPreferences() {
const response = await fetch('/api/preferences/global-preferences/');
const preferences = await response.json();
return preferences.results;
}
// Update single preference
async function updatePreference(identifier, value) {
const response = await fetch(`/api/preferences/global-preferences/${identifier}/`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({ value })
});
return response.json();
}
// Bulk update
async function bulkUpdatePreferences(preferences) {
const response = await fetch('/api/preferences/global-preferences/bulk/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({ preferences })
});
return response.json();
}
// Usage example
async function updateSiteSettings() {
await bulkUpdatePreferences({
'general__title': 'My New Site',
'general__maintenance_mode': false,
'ui__theme': 'light'
});
}from dynamic_preferences.users.models import UserPreferenceModel
from dynamic_preferences.users.serializers import UserPreferenceSerializer
from dynamic_preferences.api.viewsets import PerInstancePreferenceViewSet
from rest_framework.permissions import IsAuthenticated
class UserPreferencesViewSet(PerInstancePreferenceViewSet):
"""
API viewset for user-specific preferences.
Automatically filters preferences to current user.
"""
queryset = UserPreferenceModel.objects.all()
serializer_class = UserPreferenceSerializer
permission_classes = [IsAuthenticated]
def get_related_instance(self):
"""Return current user as the related instance."""
return self.request.user
def get_queryset(self):
"""Filter preferences to current user only."""
return super().get_queryset().filter(instance=self.request.user)
# URL configuration
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('global-preferences', GlobalPreferencesViewSet, basename='global-preferences')
router.register('user-preferences', UserPreferencesViewSet, basename='user-preferences')
urlpatterns = [
path('api/preferences/', include(router.urls)),
]from rest_framework.permissions import BasePermission
class PreferencePermission(BasePermission):
"""Custom permission for preferences based on section."""
def has_permission(self, request, view):
"""Check general permission."""
if request.method in ['GET', 'HEAD', 'OPTIONS']:
return request.user.is_authenticated
return request.user.is_staff
def has_object_permission(self, request, view, obj):
"""Check permission for specific preference."""
# Allow read access to authenticated users
if request.method in ['GET', 'HEAD', 'OPTIONS']:
return request.user.is_authenticated
# Check section-specific permissions
if obj.section == 'security':
return request.user.is_superuser
elif obj.section == 'ui':
return request.user.is_staff
return request.user.is_staff
class SectionBasedPreferenceViewSet(GlobalPreferencesViewSet):
permission_classes = [PreferencePermission]{
"count": 5,
"next": null,
"previous": null,
"results": [
{
"section": "general",
"name": "title",
"identifier": "general__title",
"value": "My Site",
"default": "Default Title",
"verbose_name": "Site Title",
"help_text": "The title displayed in the site header",
"additional_data": {},
"field": {
"class": "CharField",
"kwargs": {
"max_length": 255,
"required": true
}
}
}
]
}Install with Tessl CLI
npx tessl i tessl/pypi-django-dynamic-preferences