Hello everyone!
I wanted to start by expressing how grateful I am for this forum and community. It has been an incredible resource for my development. Big shoutout to Ken Whitesell!
I am currently using Django Model Forms (and rendering with Crispy) to create and edit instances of a model I am calling “Definitions.” This model contains many fields, most of which are properly rendered and populated in the form as they are static and do not rely on any foreign keys. However, one field is a Many-to-Many relationship that relates to another model, “Sections.”
For starters, I had to override the init method for the Model Form class associated with Definition because I am using a Manager that filters queries based on the current organization ID. The override involves creating the ManyToMany form field at initialization to comply with the OrgFilter Manager and generate correct querysets. See below for implementation.
All of this to say, when I attempt to edit/populate the form, every form field appears as expected EXCEPT the ManyToMany field. Although I set the widget to CheckboxSelectMultiple, it is rendering as a single choice Radio select. The existing selections associated with a given Definition instance also don’t initialize in the widget, so the form field appears blank as well.
Any thoughts/comments/solutions? I tried a couple different approaches based on solutions for similar problems on this and other forums, but none seem to work. If I don’t set the widget, it defaults to a single select from a drop down (which I’m pretty sure is the default select widget). Also, manually setting instance or initial in init has not seemed to work.
forms.py
class DefinitionForm(forms.ModelForm):
class Meta:
model = models.Definition
fields = "__all__"
exclude = ("org",)
def __init__(self, *args, **kwargs):
super(DefinitionForm, self).__init__(*args, **kwargs)
self.fields["sections"] = forms.ModelMultipleChoiceField(
queryset=models.Section.objects.all(),
widget=forms.CheckboxSelectMultiple,
)
views.py
def edit_definition(request, def_id):
definition = get_object_or_404(models.Definition, id=def_id, org=request.user.org)
if request.method == "POST":
form = forms.DefinitionForm(request.POST)
if form.is_valid():
form.save()
return redirect("admin_defs_sects", request)
else:
form = forms.DefinitionForm(instance=definition)
context = {
"form": form,
"definition": definition,
}
return TemplateResponse(request, "folio/edit_definition.html", context)
models.py
class Definition(OrgFilterBase):
"""Metadata definitions"""
...
sections = models.ManyToManyField("Section")
...
@property
def sections_str(self) -> str:
return ", ".join([p.name for p in self.sections.all().order_by("name")])
def __str__(self):
return f"{self.name}"
def jsonify_content(self):
"""Convert the content field to JSON"""
def replace_leading(source, char=" "):
stripped = source.lstrip()
prefix_len = len(source) - len(stripped)
return char * (2 * prefix_len) + stripped
...
def save(self, *args, **kwargs):
self.jsonify_content()
super().save(*args, **kwargs)
edit_definition.html
{% load crispy_forms_tags %}
<div class="p-4">
<h2 class="text-xl font-bold mb-4">Edit Definition</h2>
<form hx-post="{% url 'edit_definition' definition.id %}" hx-swap="innerHTML">
{% csrf_token %}
{% crispy form %}
<button type="submit" class="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-2 rounded">Save</button>
</form>
</div>