i'm at a loss, i have some code to save a modelform using the FilteredSelectMultiple widget on a many-to-many relationship. It does work on 2 out of the 4 forms. But i can't figure out why it wont work on the last 2. any help?
Here is the code for one of the 2 that is working:
forms.py:
class SubDomainForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.required = False
domains = forms.ModelMultipleChoiceField(queryset=Domain.objects.all(), label=('domains'), widget=FilteredSelectMultiple(('domains'), False, ))
subdomains = forms.ModelMultipleChoiceField(queryset=SubDomain.objects.all(), label=('subdomains'), widget=FilteredSelectMultiple(('subdomains'), False, ))
class Media:
extend = False
css = {
'all': [
'/static/admin/css/widgets.css'
]
}
js = (
'/static/assets/admin/js/django_global.js',
'/static/assets/admin/js/jquery.init.js',
'/static/assets/admin/js/core.js',
'/static/assets/admin/js/prepopulate_init.js',
'/static/assets/admin/js/prepopulate.js',
'/static/assets/admin/js/SelectBox.js',
'/static/assets/admin/js/SelectFilter2.js',
'/static/assets/admin/js/admin/RelatedObjectLookups.js',
)
class Meta:
model = SubDomain
fields = ('domains', 'subdomains', 'name', 'description')
widgets = {'description': CKEditorWidget(),}
models.py:
class SubDomain(models.Model):
domains = models.ManyToManyField(Domain, verbose_name='Link to Domain', blank=True)
subdomains = models.ManyToManyField('self', verbose_name='Link to other SubDomain', blank=True)
name = models.CharField(max_length=50)
description = RichTextField()
def __str__(self):
return f"{self.name}"
class Meta:
verbose_name_plural = ' SubDomain'
Html:
<!--- api\formfiller\templates\subdomain_form.html -->
{% load static %}
{% block head %}
<head>
{{ form.media }}
{{ form.media }}
<style>
.ck.ck-editor__main > .ck-editor__editable:not(.ck-focused) {
width: 800px;
height: 300px;
}
.ck-rounded-corners .ck.ck-editor__main > .ck-editor__editable, .ck.ck-editor__main > .ck-editor__editable.ck-rounded-corner {
width: 800px;
height: 300px;
}
.cke_top {
padding: 0px 0px 0px;
}
.fieldWrapper {
margin-left: 15px;
}
</style>
</head>
{% endblock %}
<div class="fieldWrapper related-widget-wrapper">
<form method="post" action="{% url 'submit_form' %}" id="submit-form">
<legend>Create SubDomain</legend>
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<!-- Hidden input fields for name, description, and selected SuBdomains -->
<input type="hidden" name="form_type" value="subdomain">
<input type="submit" value="Submit">
<script>
document.addEventListener('DOMContentLoaded', function () {
ClassicEditor
.create(document.querySelector('#id_description'))
.then(editor => {
console.log(editor);
})
.catch(error => {
console.error(error);
});
// Initialize the SelectFilter widget
SelectFilter.init('id_domains', "Domains", false); // Ensure 'id_domains' matches your field ID
SelectFilter.init('id_subdomains', "SubDomains", false); // Ensure 'id_subdomains' matches your field ID
document.getElementById('submit-form').addEventListener('submit', function (e) {
e.preventDefault(); // Prevent the form from submitting traditionally
// Use JavaScript Fetch or jQuery AJAX to submit the form
fetch("{% url 'submit_form' %}", {
method: 'POST',
body: new FormData(this),
headers: {
'X-Requested-With': 'XMLHttpRequest', // Indicate an AJAX request
'X-CSRFToken': '{{ csrf_token }}', // Include the CSRF token
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Show a Toastr success notification
toastr.success(data.message);
document.getElementById('submit-form').reset();
// You can also reset the form or perform other actions here
// Example: document.getElementById('submit-form').reset();
// Wait for one second before reloading the form
setTimeout(function () {
location.reload();
}, 500);
} else {
// Show a Toastr error notification
toastr.error(data.message);
}
})
.catch(error => {
// Handle AJAX error
console.error(error);
});
});
});
</script>
</form>
</div>
<footer>
<style>
.selector h2 {
margin: 0;
padding: 8px;
font-weight: 400;
font-size: 15px;
text-align: left;
background: #888;
color: white;
}
</style>
</footer>
and here is the code for one of the one's that is not working:
forms.py:
class SkillForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.required = False
subdomains = forms.ModelMultipleChoiceField(queryset=SubDomain.objects.all(), label=('subdomains'), widget=FilteredSelectMultiple(('subdomains'), False, ))
skills = forms.ModelMultipleChoiceField(queryset=Skill.objects.all(), label=('skills'), widget=FilteredSelectMultiple(('skills'), False, ))
class Media:
extend = False
css = {
'all': [
'/static/admin/css/widgets.css'
]
}
js = (
'/static/assets/admin/js/django_global.js',
'/static/assets/admin/js/jquery.init.js',
'/static/assets/admin/js/core.js',
'/static/assets/admin/js/prepopulate_init.js',
'/static/assets/admin/js/prepopulate.js',
'/static/assets/admin/js/SelectBox.js',
'/static/assets/admin/js/SelectFilter2.js',
'/static/assets/admin/js/admin/RelatedObjectLookups.js',
)
class Meta:
model = Skill
fields = ('subdomains', 'skills', 'name', 'description')
widgets = {'description': CKEditorWidget(),}
models.py:
class Skill(models.Model):
subdomain = models.ManyToManyField(SubDomain, verbose_name='Link to SubDomain', blank=True)
skill = models.ManyToManyField('self', verbose_name='Link to other skill', blank=True)
name = models.CharField(max_length=50)
description = RichTextField()
def __str__(self):
return f"{self.name}"
class Meta:
verbose_name_plural = ' Skill'
Html:
<!--- api\formfiller\templates\skill_form.html -->
{% load static %}
{% block head %}
<head>
{{ form.media }}
{{ form.media }}
<style>
.ck.ck-editor__main > .ck-editor__editable:not(.ck-focused) {
width: 800px;
height: 300px;
}
.ck-rounded-corners .ck.ck-editor__main > .ck-editor__editable, .ck.ck-editor__main > .ck-editor__editable.ck-rounded-corner {
width: 800px;
height: 300px;
}
.cke_top {
padding: 0px 0px 0px;
}
.fieldWrapper {
margin-left: 15px;
}
</style>
</head>
{% endblock %}
<div class="fieldWrapper related-widget-wrapper">
<form method="post" action="{% url 'submit_form' %}" id="submit-form">
<legend>Create Skill</legend>
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<!-- Hidden input fields for name, description, and selected Skills -->
<input type="hidden" name="form_type" value="skill">
<input type="submit" value="Submit">
<script>
document.addEventListener('DOMContentLoaded', function () {
ClassicEditor
.create(document.querySelector('#id_description'))
.then(editor => {
console.log(editor);
})
.catch(error => {
console.error(error);
});
// Initialize the SelectFilter widget
SelectFilter.init('id_subdomains', "SubDomains", false); // Ensure 'id_domains' matches your field ID
SelectFilter.init('id_skills', "Skills", false); // Ensure 'id_subdomains' matches your field ID
document.getElementById('submit-form').addEventListener('submit', function (e) {
e.preventDefault(); // Prevent the form from submitting traditionally
// Use JavaScript Fetch or jQuery AJAX to submit the form
fetch("{% url 'submit_form' %}", {
method: 'POST',
body: new FormData(this),
headers: {
'X-Requested-With': 'XMLHttpRequest', // Indicate an AJAX request
'X-CSRFToken': '{{ csrf_token }}', // Include the CSRF token
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Show a Toastr success notification
toastr.success(data.message);
document.getElementById('submit-form').reset();
// You can also reset the form or perform other actions here
// Example: document.getElementById('submit-form').reset();
// Wait for one second before reloading the form
setTimeout(function () {
location.reload();
}, 500);
} else {
// Show a Toastr error notification
toastr.error(data.message);
}
})
.catch(error => {
// Handle AJAX error
console.error(error);
});
});
});
</script>
</form>
</div>
<footer>
<style>
.selector h2 {
margin: 0;
padding: 8px;
font-weight: 400;
font-size: 15px;
text-align: left;
background: #888;
color: white;
}
</style>
</footer>
and here is the views.py:
#handle form submission
def submit_form(request):
if request.method == 'POST':
form_type = request.POST.get('form_type')
print(f"Form type: {form_type}")
# Print the entire POST data dictionary
print(request.POST)
# Define a dictionary to map form types to form classes
form_type_mapping = {
'domain': (DomainForm, Domain),
'subdomain': (SubDomainForm, SubDomain),
'skill': (SkillForm, Skill),
'tool': (ToolForm, Tool),
}
# Check if the form_type is valid
if form_type in form_type_mapping:
FormClass, model_class = form_type_mapping[form_type]
form = FormClass(request.POST, request.FILES)
if form.is_valid():
# Process and save the form data to the corresponding model
instance = form.save(commit=False)
instance.name = form.cleaned_data['name']
instance.description = form.cleaned_data['description']
instance.save()
# Save many-to-many relationships
form.save_m2m()
# Return a JSON response indicating success
return JsonResponse({'success': True, 'message': 'Form submitted successfully'})
else:
return JsonResponse({'success': False, 'message': 'Invalid form_type'})
# Handle other cases and errors
return JsonResponse({'success': False, 'message': 'Invalid request method'})
i've tried multiple ways to save the form, however it only saves 2 of them. i'm recieving the post data correctly as displayed by these prints in the console:
Working:
Form type: subdomain
<QueryDict: {'csrfmiddlewaretoken': ['TcxcKoJ7PHIkKQC9PpfLJH9kIx5cMvQjzLMHBhun3s5Wb9j3wntJViiuBi68haqC'], 'domains': ['1'], 'subdomains': ['1'], 'name': ['Test SubDomain name'], 'description': ['<p>Test SubDomain description</p>'], 'form_type': ['subdomain']}>
Not working:
Form type: skill
<QueryDict: {'csrfmiddlewaretoken': ['9vFGK25Y4x86O8iSc40lxGl7AqvxIHieP4UbBVQeiivIfrZMT2ejJhuhtbwtdmSx'], 'subdomains': ['1'], 'skills': ['1'], 'name': ['test skill name'], 'description': ['<p>test skill description</p>'], 'form_type': ['skill']}>
Solved:
was missing an S in my models.
subdomain**s** = models.ManyToManyField(SubDomain, verbose_name='Link to SubDomain', blank=True)
skill**s** = models.ManyToManyField('self', verbose_name='Link to other skill', blank=True)