or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

admin-integration.mdcore-models.mddjango-integration.mdforms-views.mdindex.mdpreference-types.mdregistries.mdrest-api.mdserialization.mdsignals.mduser-preferences.md

rest-api.mddocs/

0

# REST API

1

2

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.

3

4

## Capabilities

5

6

### Preference Serializers

7

8

Serializers for converting preference objects to/from JSON representations with proper validation and type handling.

9

10

```python { .api }

11

class PreferenceValueField(serializers.Field):

12

"""

13

Custom DRF field for handling preference value serialization/deserialization.

14

15

Automatically handles type conversion based on the preference type

16

and validates values according to preference validation rules.

17

"""

18

19

def to_representation(self, value):

20

"""Convert preference value to API representation."""

21

22

def to_internal_value(self, data):

23

"""Convert API data to preference value."""

24

25

class PreferenceSerializer(serializers.Serializer):

26

"""

27

Base serializer for preference objects.

28

29

Fields:

30

- section (CharField, read-only): Preference section

31

- name (CharField, read-only): Preference name

32

- identifier (SerializerMethodField): Section__name identifier

33

- default (SerializerMethodField): Default value

34

- value (PreferenceValueField): Current preference value

35

- verbose_name (SerializerMethodField): Display name

36

- help_text (SerializerMethodField): Help text

37

- additional_data (SerializerMethodField): Additional metadata

38

- field (SerializerMethodField): Form field configuration

39

"""

40

section = serializers.CharField(read_only=True)

41

name = serializers.CharField(read_only=True)

42

identifier = serializers.SerializerMethodField()

43

default = serializers.SerializerMethodField()

44

value = PreferenceValueField()

45

verbose_name = serializers.SerializerMethodField()

46

help_text = serializers.SerializerMethodField()

47

additional_data = serializers.SerializerMethodField()

48

field = serializers.SerializerMethodField()

49

50

def get_identifier(self, obj) -> str:

51

"""Return section__name identifier."""

52

53

def get_default(self, obj):

54

"""Return default value."""

55

56

def get_verbose_name(self, obj) -> str:

57

"""Return verbose name."""

58

59

def get_help_text(self, obj) -> str:

60

"""Return help text."""

61

62

def get_additional_data(self, obj) -> dict:

63

"""Return additional preference metadata."""

64

65

def get_field(self, obj) -> dict:

66

"""Return form field configuration."""

67

68

def validate_value(self, value):

69

"""

70

Validate preference value using form field validation.

71

72

Args:

73

- value: Value to validate

74

75

Returns:

76

Validated value

77

78

Raises:

79

- ValidationError: If value is invalid

80

"""

81

82

def update(self, instance, validated_data):

83

"""

84

Update preference value and send signal.

85

86

Args:

87

- instance: Preference instance

88

- validated_data: Validated data

89

90

Returns:

91

Updated preference instance

92

"""

93

94

class GlobalPreferenceSerializer(PreferenceSerializer):

95

"""

96

Serializer for global preferences.

97

98

Inherits all functionality from PreferenceSerializer

99

with global preference specific configuration.

100

"""

101

```

102

103

### Preference ViewSets

104

105

ViewSets providing API endpoints for preference CRUD operations with proper permissions and bulk operations.

106

107

```python { .api }

108

class PreferenceViewSet(

109

mixins.UpdateModelMixin,

110

mixins.ListModelMixin,

111

mixins.RetrieveModelMixin,

112

viewsets.GenericViewSet

113

):

114

"""

115

Base API viewset for preference CRUD operations.

116

117

Provides:

118

- GET /preferences/ - List all preferences

119

- GET /preferences/{identifier}/ - Retrieve specific preference

120

- PUT/PATCH /preferences/{identifier}/ - Update preference value

121

- POST /preferences/bulk/ - Bulk update multiple preferences

122

"""

123

serializer_class = PreferenceSerializer

124

lookup_field = 'identifier'

125

126

def get_queryset(self):

127

"""Initialize and filter preferences."""

128

129

def get_manager(self) -> PreferencesManager:

130

"""Return preference manager for this viewset."""

131

132

def init_preferences(self):

133

"""Ensure preferences are populated in database."""

134

135

def get_object(self):

136

"""

137

Get preference by identifier (section__name format).

138

139

Returns:

140

Preference instance

141

142

Raises:

143

- Http404: If preference not found

144

"""

145

146

def get_section_and_name(self, identifier: str) -> tuple:

147

"""

148

Parse preference identifier into section and name.

149

150

Args:

151

- identifier: Preference identifier (section__name)

152

153

Returns:

154

Tuple of (section, name)

155

"""

156

157

@action(detail=False, methods=['post'])

158

def bulk(self, request):

159

"""

160

Bulk update multiple preferences atomically.

161

162

Request Body:

163

{

164

"preferences": {

165

"section__name": value,

166

"section__name2": value2,

167

...

168

}

169

}

170

171

Returns:

172

Updated preferences data

173

"""

174

175

class GlobalPreferencesViewSet(PreferenceViewSet):

176

"""

177

API viewset for global preferences.

178

179

Endpoints:

180

- GET /global-preferences/ - List global preferences

181

- GET /global-preferences/{identifier}/ - Get specific preference

182

- PUT /global-preferences/{identifier}/ - Update preference

183

- POST /global-preferences/bulk/ - Bulk update

184

"""

185

queryset = GlobalPreferenceModel.objects.all()

186

serializer_class = GlobalPreferenceSerializer

187

permission_classes = [GlobalPreferencePermission]

188

189

class PerInstancePreferenceViewSet(PreferenceViewSet):

190

"""

191

Base viewset for per-instance preferences.

192

193

Subclasses must implement get_related_instance() method

194

to return the instance for preference filtering.

195

"""

196

197

def get_manager(self, **kwargs) -> PreferencesManager:

198

"""Return manager with instance context."""

199

200

def get_queryset(self):

201

"""Filter preferences by related instance."""

202

203

def get_related_instance(self):

204

"""

205

Abstract method to get the related instance.

206

207

Must be implemented by subclasses.

208

209

Returns:

210

Related model instance

211

"""

212

```

213

214

### API Permissions

215

216

Permission classes for controlling access to preference API endpoints.

217

218

```python { .api }

219

class GlobalPreferencePermission(DjangoModelPermissions):

220

"""

221

Permission class for global preferences API.

222

223

Controls access to global preference endpoints based on

224

Django model permissions for GlobalPreferenceModel.

225

"""

226

227

def has_permission(self, request, view):

228

"""Check if user has permission for the action."""

229

230

def has_object_permission(self, request, view, obj):

231

"""Check if user has permission for specific preference."""

232

```

233

234

## Usage Examples

235

236

### API Client Usage

237

238

```python

239

import requests

240

from django.conf import settings

241

242

# Base API URL

243

api_base = f"{settings.BASE_URL}/api/preferences/"

244

245

# List all global preferences

246

response = requests.get(f"{api_base}global-preferences/")

247

preferences = response.json()

248

249

# Get specific preference

250

response = requests.get(f"{api_base}global-preferences/general__title/")

251

preference = response.json()

252

print(preference['value']) # Current value

253

print(preference['default']) # Default value

254

255

# Update preference

256

response = requests.patch(

257

f"{api_base}global-preferences/general__title/",

258

json={'value': 'New Site Title'},

259

headers={'Content-Type': 'application/json'}

260

)

261

262

# Bulk update multiple preferences

263

response = requests.post(

264

f"{api_base}global-preferences/bulk/",

265

json={

266

'preferences': {

267

'general__title': 'New Title',

268

'general__maintenance_mode': True,

269

'ui__theme': 'dark'

270

}

271

}

272

)

273

```

274

275

### JavaScript/Frontend Usage

276

277

```javascript

278

// Fetch preferences

279

async function getPreferences() {

280

const response = await fetch('/api/preferences/global-preferences/');

281

const preferences = await response.json();

282

return preferences.results;

283

}

284

285

// Update single preference

286

async function updatePreference(identifier, value) {

287

const response = await fetch(`/api/preferences/global-preferences/${identifier}/`, {

288

method: 'PATCH',

289

headers: {

290

'Content-Type': 'application/json',

291

'X-CSRFToken': getCsrfToken()

292

},

293

body: JSON.stringify({ value })

294

});

295

return response.json();

296

}

297

298

// Bulk update

299

async function bulkUpdatePreferences(preferences) {

300

const response = await fetch('/api/preferences/global-preferences/bulk/', {

301

method: 'POST',

302

headers: {

303

'Content-Type': 'application/json',

304

'X-CSRFToken': getCsrfToken()

305

},

306

body: JSON.stringify({ preferences })

307

});

308

return response.json();

309

}

310

311

// Usage example

312

async function updateSiteSettings() {

313

await bulkUpdatePreferences({

314

'general__title': 'My New Site',

315

'general__maintenance_mode': false,

316

'ui__theme': 'light'

317

});

318

}

319

```

320

321

### Custom ViewSet for User Preferences

322

323

```python

324

from dynamic_preferences.users.models import UserPreferenceModel

325

from dynamic_preferences.users.serializers import UserPreferenceSerializer

326

from dynamic_preferences.api.viewsets import PerInstancePreferenceViewSet

327

from rest_framework.permissions import IsAuthenticated

328

329

class UserPreferencesViewSet(PerInstancePreferenceViewSet):

330

"""

331

API viewset for user-specific preferences.

332

333

Automatically filters preferences to current user.

334

"""

335

queryset = UserPreferenceModel.objects.all()

336

serializer_class = UserPreferenceSerializer

337

permission_classes = [IsAuthenticated]

338

339

def get_related_instance(self):

340

"""Return current user as the related instance."""

341

return self.request.user

342

343

def get_queryset(self):

344

"""Filter preferences to current user only."""

345

return super().get_queryset().filter(instance=self.request.user)

346

347

# URL configuration

348

from rest_framework.routers import DefaultRouter

349

350

router = DefaultRouter()

351

router.register('global-preferences', GlobalPreferencesViewSet, basename='global-preferences')

352

router.register('user-preferences', UserPreferencesViewSet, basename='user-preferences')

353

354

urlpatterns = [

355

path('api/preferences/', include(router.urls)),

356

]

357

```

358

359

### Custom Permission Classes

360

361

```python

362

from rest_framework.permissions import BasePermission

363

364

class PreferencePermission(BasePermission):

365

"""Custom permission for preferences based on section."""

366

367

def has_permission(self, request, view):

368

"""Check general permission."""

369

if request.method in ['GET', 'HEAD', 'OPTIONS']:

370

return request.user.is_authenticated

371

return request.user.is_staff

372

373

def has_object_permission(self, request, view, obj):

374

"""Check permission for specific preference."""

375

# Allow read access to authenticated users

376

if request.method in ['GET', 'HEAD', 'OPTIONS']:

377

return request.user.is_authenticated

378

379

# Check section-specific permissions

380

if obj.section == 'security':

381

return request.user.is_superuser

382

elif obj.section == 'ui':

383

return request.user.is_staff

384

385

return request.user.is_staff

386

387

class SectionBasedPreferenceViewSet(GlobalPreferencesViewSet):

388

permission_classes = [PreferencePermission]

389

```

390

391

### API Response Format

392

393

```json

394

{

395

"count": 5,

396

"next": null,

397

"previous": null,

398

"results": [

399

{

400

"section": "general",

401

"name": "title",

402

"identifier": "general__title",

403

"value": "My Site",

404

"default": "Default Title",

405

"verbose_name": "Site Title",

406

"help_text": "The title displayed in the site header",

407

"additional_data": {},

408

"field": {

409

"class": "CharField",

410

"kwargs": {

411

"max_length": 255,

412

"required": true

413

}

414

}

415

}

416

]

417

}

418

```