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

forms-views.mddocs/

0

# Forms and Views

1

2

Django forms and views for web-based preference editing with dynamic form generation and validation. This provides comprehensive web interface components for managing preferences through standard Django forms and views.

3

4

## Capabilities

5

6

### Single Preference Forms

7

8

Forms for editing individual preferences with automatic field generation based on preference type.

9

10

```python { .api }

11

class AbstractSinglePreferenceForm(forms.ModelForm):

12

"""

13

Base form for editing single preferences.

14

15

Automatically generates appropriate form field based on

16

preference type and handles validation and saving.

17

"""

18

19

def __init__(self, *args, **kwargs):

20

"""

21

Initialize form with preference-specific field.

22

23

Args:

24

- *args: Standard form args

25

- **kwargs: Standard form kwargs plus preference info

26

"""

27

28

def clean(self):

29

"""

30

Validate preference exists in registry and value is valid.

31

32

Returns:

33

Cleaned form data

34

35

Raises:

36

- ValidationError: If preference not found or value invalid

37

"""

38

39

def save(self, commit=True):

40

"""

41

Save preference value through preference system.

42

43

Args:

44

- commit: Whether to save to database immediately

45

46

Returns:

47

Preference model instance

48

"""

49

50

class SinglePerInstancePreferenceForm(AbstractSinglePreferenceForm):

51

"""

52

Form for editing single per-instance preferences.

53

54

Handles preferences tied to specific model instances,

55

such as user-specific preferences.

56

"""

57

class Meta:

58

model = None # Set by subclasses

59

fields = '__all__'

60

61

class GlobalSinglePreferenceForm(AbstractSinglePreferenceForm):

62

"""

63

Form for editing single global preferences.

64

65

Specialized for global (site-wide) preferences with

66

appropriate model and field configuration.

67

"""

68

class Meta:

69

model = GlobalPreferenceModel

70

fields = '__all__'

71

```

72

73

### Multiple Preference Forms

74

75

Forms for editing multiple preferences simultaneously with section organization and bulk updates.

76

77

```python { .api }

78

class PreferenceForm(forms.Form):

79

"""

80

Base form for editing multiple preferences.

81

82

Dynamically generates fields for multiple preferences

83

and handles bulk updates through preference managers.

84

85

Attributes:

86

- registry: Associated preference registry

87

"""

88

registry = None

89

90

def __init__(self, *args, **kwargs):

91

"""

92

Initialize form with preference fields.

93

94

Args:

95

- *args: Standard form args

96

- **kwargs: Standard form kwargs plus preferences/section info

97

"""

98

99

def update_preferences(self, **kwargs):

100

"""

101

Update multiple preferences from form data.

102

103

Args:

104

- **kwargs: Additional options for preference manager

105

106

Returns:

107

Dictionary of updated preferences

108

"""

109

110

class GlobalPreferenceForm(PreferenceForm):

111

"""

112

Form for editing multiple global preferences.

113

114

Provides interface for bulk editing of global preferences

115

with proper validation and section organization.

116

"""

117

registry = global_preferences_registry

118

```

119

120

### Dynamic Form Builders

121

122

Factory functions for creating dynamic preference forms based on registry configuration and filtering options.

123

124

```python { .api }

125

def preference_form_builder(form_base_class, preferences=None, **kwargs):

126

"""

127

Build dynamic forms for preferences.

128

129

Creates form classes dynamically based on registry configuration

130

and filtering criteria. Supports section filtering, instance binding,

131

and custom field configuration.

132

133

Args:

134

- form_base_class: Base form class with registry attribute

135

- preferences (list): Specific preferences to include (optional)

136

- **kwargs: Additional options:

137

- section (str): Filter by section

138

- instance: Instance for per-instance preferences

139

- exclude (list): Preferences to exclude

140

- field_kwargs (dict): Custom field arguments

141

142

Returns:

143

Dynamically created form class

144

"""

145

146

def global_preference_form_builder(preferences=None, **kwargs):

147

"""

148

Shortcut for building global preference forms.

149

150

Args:

151

- preferences (list): Specific preferences to include (optional)

152

- **kwargs: Additional options (section, exclude, etc.)

153

154

Returns:

155

Dynamic GlobalPreferenceForm class

156

"""

157

```

158

159

### Preference Views

160

161

Django views for displaying and editing preferences with proper permissions and form handling.

162

163

```python { .api }

164

class RegularTemplateView(TemplateView):

165

"""

166

Simple template view for testing preference context.

167

168

Template: "dynamic_preferences/testcontext.html"

169

"""

170

template_name = "dynamic_preferences/testcontext.html"

171

172

class PreferenceFormView(FormView):

173

"""

174

Display form for updating preferences by section.

175

176

Provides complete view for preference editing with

177

section filtering, form generation, and success handling.

178

179

Attributes:

180

- registry: Registry for preference lookups

181

- form_class: Form class for preference updates

182

- template_name: "dynamic_preferences/form.html"

183

"""

184

registry = None

185

form_class = None

186

template_name = "dynamic_preferences/form.html"

187

success_url = None

188

189

def dispatch(self, request, *args, **kwargs):

190

"""

191

Setup section from URL arguments.

192

193

Args:

194

- request: HTTP request

195

- *args: URL positional arguments

196

- **kwargs: URL keyword arguments (including section)

197

198

Returns:

199

HTTP response

200

"""

201

202

def get_form_class(self):

203

"""

204

Build dynamic form class based on section and registry.

205

206

Returns:

207

Form class configured for current section/preferences

208

"""

209

210

def get_context_data(self, **kwargs):

211

"""

212

Add registry and section information to template context.

213

214

Args:

215

- **kwargs: Base context data

216

217

Returns:

218

Updated context dictionary

219

"""

220

221

def form_valid(self, form):

222

"""

223

Update preferences on successful form submission.

224

225

Args:

226

- form: Valid form instance

227

228

Returns:

229

HTTP response (redirect to success URL)

230

"""

231

```

232

233

## Usage Examples

234

235

### Basic Preference Form View

236

237

```python

238

from dynamic_preferences.views import PreferenceFormView

239

from dynamic_preferences.registries import global_preferences_registry

240

241

class GlobalPreferenceView(PreferenceFormView):

242

"""View for editing global preferences."""

243

registry = global_preferences_registry

244

template_name = 'admin/preferences/global_form.html'

245

success_url = '/preferences/global/'

246

247

def dispatch(self, request, *args, **kwargs):

248

# Check permissions

249

if not request.user.is_staff:

250

return HttpResponseForbidden()

251

return super().dispatch(request, *args, **kwargs)

252

253

# URL configuration

254

urlpatterns = [

255

path('preferences/global/', GlobalPreferenceView.as_view(), name='global_preferences'),

256

path('preferences/global/<str:section>/', GlobalPreferenceView.as_view(), name='global_preferences_section'),

257

]

258

```

259

260

### Custom Form with Validation

261

262

```python

263

from dynamic_preferences.forms import global_preference_form_builder

264

from django.core.exceptions import ValidationError

265

266

def custom_preference_view(request):

267

"""Custom view with custom form validation."""

268

269

# Build form for specific section

270

PreferenceForm = global_preference_form_builder(section='general')

271

272

if request.method == 'POST':

273

form = PreferenceForm(request.POST)

274

if form.is_valid():

275

# Custom validation

276

if form.cleaned_data.get('general__maintenance_mode') and not request.user.is_superuser:

277

form.add_error(None, 'Only superusers can enable maintenance mode')

278

else:

279

form.update_preferences()

280

messages.success(request, 'Preferences updated successfully!')

281

return redirect('preferences')

282

else:

283

form = PreferenceForm()

284

285

return render(request, 'preferences_form.html', {'form': form})

286

287

# Advanced form with custom field configuration

288

def advanced_preference_view(request):

289

"""View with custom field configuration."""

290

291

PreferenceForm = global_preference_form_builder(

292

preferences=['general__title', 'general__description'],

293

field_kwargs={

294

'general__title': {

295

'widget': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter site title'})

296

},

297

'general__description': {

298

'widget': forms.Textarea(attrs={'class': 'form-control', 'rows': 4})

299

}

300

}

301

)

302

303

if request.method == 'POST':

304

form = PreferenceForm(request.POST)

305

if form.is_valid():

306

form.update_preferences()

307

return JsonResponse({'success': True})

308

else:

309

return JsonResponse({'success': False, 'errors': form.errors})

310

else:

311

form = PreferenceForm()

312

313

return render(request, 'advanced_preferences.html', {'form': form})

314

```

315

316

### Section-Based Preference Views

317

318

```python

319

class SectionPreferenceView(PreferenceFormView):

320

"""View for editing preferences by section."""

321

registry = global_preferences_registry

322

template_name = 'preferences/section_form.html'

323

324

def get_success_url(self):

325

section = self.kwargs.get('section')

326

return reverse('section_preferences', kwargs={'section': section})

327

328

def get_context_data(self, **kwargs):

329

context = super().get_context_data(**kwargs)

330

section = self.kwargs.get('section')

331

332

# Add section information

333

context['section'] = section

334

context['section_preferences'] = self.registry.preferences(section=section)

335

336

# Add navigation for other sections

337

context['all_sections'] = self.registry.sections()

338

339

return context

340

341

# Multiple section views

342

class PreferencesIndexView(TemplateView):

343

"""Index view showing all preference sections."""

344

template_name = 'preferences/index.html'

345

346

def get_context_data(self, **kwargs):

347

context = super().get_context_data(**kwargs)

348

context['sections'] = global_preferences_registry.sections()

349

return context

350

351

# URL patterns

352

urlpatterns = [

353

path('preferences/', PreferencesIndexView.as_view(), name='preferences_index'),

354

path('preferences/<str:section>/', SectionPreferenceView.as_view(), name='section_preferences'),

355

]

356

```

357

358

### AJAX Preference Updates

359

360

```python

361

from django.http import JsonResponse

362

from django.views.decorators.csrf import csrf_exempt

363

from django.utils.decorators import method_decorator

364

import json

365

366

@method_decorator(csrf_exempt, name='dispatch')

367

class AjaxPreferenceUpdateView(View):

368

"""AJAX view for updating individual preferences."""

369

370

def post(self, request):

371

try:

372

data = json.loads(request.body)

373

preference_key = data.get('preference')

374

value = data.get('value')

375

376

if not preference_key or value is None:

377

return JsonResponse({'error': 'Missing preference or value'}, status=400)

378

379

# Update preference

380

global_preferences = global_preferences_registry.manager()

381

global_preferences[preference_key] = value

382

383

return JsonResponse({

384

'success': True,

385

'preference': preference_key,

386

'value': value,

387

'message': 'Preference updated successfully'

388

})

389

390

except Exception as e:

391

return JsonResponse({'error': str(e)}, status=500)

392

393

# JavaScript for AJAX updates

394

"""

395

async function updatePreference(key, value) {

396

const response = await fetch('/preferences/ajax-update/', {

397

method: 'POST',

398

headers: {

399

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

400

'X-CSRFToken': getCsrfToken()

401

},

402

body: JSON.stringify({

403

preference: key,

404

value: value

405

})

406

});

407

408

const result = await response.json();

409

if (result.success) {

410

showMessage('Preference updated!', 'success');

411

} else {

412

showMessage('Error: ' + result.error, 'error');

413

}

414

}

415

416

// Update preference on change

417

document.addEventListener('change', function(e) {

418

if (e.target.classList.contains('preference-field')) {

419

const key = e.target.dataset.preference;

420

const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

421

updatePreference(key, value);

422

}

423

});

424

"""

425

```

426

427

### Form Widgets and Customization

428

429

```python

430

from django import forms

431

from dynamic_preferences.forms import global_preference_form_builder

432

433

def custom_widget_view(request):

434

"""View with custom widgets for different preference types."""

435

436

# Custom widget configuration

437

widget_config = {

438

'ui__theme': forms.Select(attrs={'class': 'form-select'}),

439

'ui__primary_color': forms.TextInput(attrs={'type': 'color', 'class': 'form-control'}),

440

'general__description': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}),

441

'general__logo': forms.FileInput(attrs={'class': 'form-control', 'accept': 'image/*'}),

442

}

443

444

PreferenceForm = global_preference_form_builder(

445

section='ui',

446

field_kwargs={

447

key: {'widget': widget} for key, widget in widget_config.items()

448

}

449

)

450

451

if request.method == 'POST':

452

form = PreferenceForm(request.POST, request.FILES)

453

if form.is_valid():

454

form.update_preferences()

455

return redirect('ui_preferences')

456

else:

457

form = PreferenceForm()

458

459

return render(request, 'ui_preferences.html', {'form': form})

460

461

# Custom form class with additional methods

462

class ExtendedPreferenceForm(forms.Form):

463

"""Extended preference form with additional functionality."""

464

registry = global_preferences_registry

465

466

def __init__(self, *args, **kwargs):

467

self.section = kwargs.pop('section', None)

468

super().__init__(*args, **kwargs)

469

self.setup_preference_fields()

470

471

def setup_preference_fields(self):

472

"""Setup fields based on preferences."""

473

preferences = self.registry.preferences(section=self.section)

474

for pref in preferences:

475

field = pref.setup_field()

476

self.fields[pref.identifier()] = field

477

478

def clean(self):

479

"""Custom validation across preferences."""

480

cleaned_data = super().clean()

481

482

# Example: Validate theme and color compatibility

483

theme = cleaned_data.get('ui__theme')

484

primary_color = cleaned_data.get('ui__primary_color')

485

486

if theme == 'dark' and primary_color and primary_color.startswith('#F'):

487

self.add_error('ui__primary_color', 'Light colors not recommended for dark theme')

488

489

return cleaned_data

490

491

def save(self):

492

"""Save all preferences."""

493

manager = self.registry.manager()

494

for key, value in self.cleaned_data.items():

495

manager[key] = value

496

return manager

497

```

498

499

### Template Usage

500

501

```html

502

<!-- preferences/section_form.html -->

503

<form method="post">

504

{% csrf_token %}

505

506

<h2>{{ section|title }} Preferences</h2>

507

508

{% for field in form %}

509

<div class="form-group">

510

<label for="{{ field.id_for_label }}">{{ field.label }}</label>

511

{{ field }}

512

{% if field.help_text %}

513

<small class="form-text text-muted">{{ field.help_text }}</small>

514

{% endif %}

515

{% if field.errors %}

516

<div class="text-danger">

517

{% for error in field.errors %}

518

<small>{{ error }}</small>

519

{% endfor %}

520

</div>

521

{% endif %}

522

</div>

523

{% endfor %}

524

525

<button type="submit" class="btn btn-primary">Save Preferences</button>

526

</form>

527

528

<!-- Section navigation -->

529

<nav class="mt-4">

530

<h4>Other Sections</h4>

531

<ul class="list-unstyled">

532

{% for section in all_sections %}

533

<li><a href="{% url 'section_preferences' section.name %}">{{ section.verbose_name|default:section.name }}</a></li>

534

{% endfor %}

535

</ul>

536

</nav>

537

```