CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-dynamic-preferences

Dynamic global and instance settings for your django project

Pending
Overview
Eval results
Files

rest-api.mddocs/

REST API

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.

Capabilities

Preference Serializers

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.
    """

Preference ViewSets

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
        """

API Permissions

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."""

Usage Examples

API Client Usage

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'
        }
    }
)

JavaScript/Frontend Usage

// 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'
    });
}

Custom ViewSet for User Preferences

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)),
]

Custom Permission Classes

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]

API Response Format

{
  "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

docs

admin-integration.md

core-models.md

django-integration.md

forms-views.md

index.md

preference-types.md

registries.md

rest-api.md

serialization.md

signals.md

user-preferences.md

tile.json