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