Custom Django Admin Interface with Grouped Forms
Based on your complex admin interface requirements, you'll need to create a custom solution that combines your regrouped display with editable form fields. Here's an approach to integrate those tick-boxes alongside your grouped conditions:
1. Create Custom ModelAdmin with Form Preparation
You'll need to override the get_form
method to prepare your custom form with the necessary data:
class ProjectAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
# Get the base form
form = super().get_form(request, obj, **kwargs)
if obj: # Only for existing projects
# Fetch all related data
objective_conditions = ProjectObjectiveCondition.objects.filter(project=obj)
work_cycles = WorkCycle.objects.all() # Or filter as needed
# Prepare data for template
self.prepared_data = self.prepare_grouped_data(objective_conditions, work_cycles, obj)
return form
def prepare_grouped_data(self, conditions, work_cycles, project):
# Group conditions by objective and level
# Also attach commitment data for each work cycle
grouped_data = {}
# Your grouping logic here
# ...
return grouped_data
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or {}
if hasattr(self, 'prepared_data'):
extra_context['prepared_data'] = self.prepared_data
extra_context['work_cycles'] = WorkCycle.objects.all() # Or filter as needed
return super().change_view(request, object_id, form_url, extra_context)
2. Create a Custom Template
You'll need to modify your template to include form fields for each commitment:
{% extends "admin/change_form.html" %}
{% load admin_urls %}
{% block field_sets %}
{{ block.super }}
{# Your custom grouped display #}
<div class="grouped-conditions">
{% for objective, levels in prepared_data.items %}
<div class="objective-group">
<h3>{{ objective.name }}</h3>
{% for level, conditions in levels.items %}
<div class="level-group">
<h4>{{ level.name }}</h4>
{# Commitment checkboxes for this level #}
<div class="commitment-checkboxes">
{% for cycle in work_cycles %}
<div class="cycle-checkbox">
<label>{{ cycle.year }}</label>
<input type="checkbox"
name="commitment_{{ objective.id }}_{{ level.id }}_{{ cycle.id }}"
{% if level.commitments|get_item:cycle.id %}checked{% endif %}>
</div>
{% endfor %}
</div>
{# Conditions list #}
<ul class="conditions-list">
{% for condition in conditions %}
<li>{{ condition.text }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endblock %}
3. Process the Form Submission
You'll need to override the save_model
method to handle your custom form fields:
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
# Process commitment checkboxes
if change: # Only for existing objects
for key, value in request.POST.items():
if key.startswith('commitment_'):
# Parse the field name to get IDs
_, objective_id, level_id, cycle_id = key.split('_')
# Find or create the commitment
commitment, created = LevelCommitment.objects.get_or_create(
project=obj,
objective_id=objective_id,
level_id=level_id,
work_cycle_id=cycle_id,
defaults={'is_committed': value == 'on'}
)
if not created:
# Update existing commitment
commitment.is_committed = value == 'on'
commitment.save()
4. Add Custom JavaScript for Better UX
You might want to add some JavaScript to enhance the user experience:
// Add this to your admin.js or inline in the template
document.addEventListener('DOMContentLoaded', function() {
// Add any interactive behaviors for your checkboxes
const checkboxes = document.querySelectorAll('.commitment-checkboxes input[type="checkbox"]');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
// Optional: Add visual feedback when changed
this.closest('.cycle-checkbox').classList.add('changed');
});
});
});
Alternative Approach: Custom Formsets
If the above approach becomes too complex, you might consider using Django's formset factory to create multiple forms:
from django.forms import formset_factory, Form, BooleanField
class CommitmentForm(Form):
is_committed = BooleanField(required=False)
# Hidden fields to identify the relationship
objective_id = forms.IntegerField(widget=forms.HiddenInput())
level_id = forms.IntegerField(widget=forms.HiddenInput())
cycle_id = forms.IntegerField(widget=forms.HiddenInput())
# In your ModelAdmin
def get_formsets_with_inlines(self, request, obj=None):
# First yield the standard formsets/inlines
yield from super().get_formsets_with_inlines(request, obj)
if obj:
# Create a custom formset for commitments
CommitmentFormSet = formset_factory(CommitmentForm, extra=0)
# Prepare initial data
initial_data = []
# ... populate initial_data based on your objectives, levels, and cycles
formset = CommitmentFormSet(initial=initial_data, prefix='commitments')
# You'll need to handle this formset in your template and in save_model
yield formset, None # None instead of an inline
This is a complex customization that will require careful implementation and testing. You might need to adjust the approach based on your specific model structure and requirements.