I have a Django form that receives entries from the users with information on a surgical procedure. Each Procedure will have only one (surgical) Technique and only one Diagnosis. Each Technique may be related to a limited number of Diagnosis, and each Diagnosis may be used on different Techniques. I want to limit which Diagnosis appear of the form based on which Technique the user selected previously on the form.
I tried using smart_selects ChainedManyToMany field with relative success, but it enable multiple Diagnosis to be selected, I only want to have one.
I`m also using DAL for autocompleting the Technique (over 1,6k options) as the user types.
My models:
# The "Technique" model
class Sigtap(models.Model):
codigo = models.CharField(max_length=10)
descricao = models.CharField(max_length=175, default='')
# The "Diagnosis" model
class Cid10(models.Model):
codigo = models.CharField(max_length=4)
descricao = models.CharField(max_length=270, default='')
sigtap_compativel = models.ManyToManyField(Sigtap, blank=True)
# The "Surgical Procedure" model
class Cirurgia(models.Model):
paciente = models.PositiveIntegerField(verbose_name='Número do prontuário')
data = models.DateField(verbose_name='Data de realização')
procedimento = models.ForeignKey(Sigtap, on_delete=models.CASCADE,
verbose_name='Código do procedimento (SIGTAP)')
cid = ChainedManyToManyField(
Cid10, horizontal=True, chained_field='procedimento', chained_model_field='sigtap_compativel', auto_choose=True, verbose_name='CID-10')
My forms:
class CirurgiaModelForm(forms.ModelForm):
class Meta:
model = Cirurgia
fields = ['paciente', 'data', 'procedimento', 'cid']
widgets = {
'procedimento': autocomplete.ModelSelect2(url='sigtap-autocomplete'),
}
How can I get the form field to show only the Diagnosis related to the Technique selected and allow only one option?
CodePudding user response:
Your way of naming fields and classes confused me a lot, because the name of the classes does not represent what they really are...and also there is this:
procedimento = models.ForeignKey(Sigtap, on_delete=models.CASCADE,
verbose_name='Código do procedimento (SIGTAP)')
where you change the name I don't know for what reason, if you are going with codes like cid
and sigtap
just stick with it. Really bad practice overall.
Anyway, what you need is to use AJAX to send a request to a view where you filter a Model
based on a given id
, then use that data back in the template. I was not able to fit this solution into a ModelForm
because of the nature of the relationship (will need to look into that) so instead I used a <select>
tag inside the HTML form:
forms.py:
class CirurgiaModelForm(forms.ModelForm):
procedimento = forms.ModelChoiceField(queryset=Sigtap.objects.all(), widget=forms.Select({'onchange' : "myFunction(this.value);"}))
class Meta:
model = Cirurgia
fields = ['paciente', 'data', 'procedimento']
widgets = {
'data': forms.DateInput(attrs={'type': 'date'}),
}
views.py
import json
from django.http import JsonResponse
def surgery(request):
if request.method == 'POST':
form = CirurgiaModelForm(request.POST)
if form.is_valid():
try:
cid = Cid10.objects.get(id=request.POST.get('cid'))
cirurgia = Cirurgia.objects.create(**form.cleaned_data)
cirurgia.cid.add(cid)
except ObjectDoesNotExist:
pass
return redirect('/surgery/')
else:
form = CirurgiaModelForm()
return render(request, 'surgery.html', {'form': form})
def filter_diagnosis_ajax(request):
data = json.loads(request.body)
procedimento = Sigtap.objects.get(id=data['technique_id'])
cid_list = list(Cid10.objects.filter(sigtap_compativel=procedimento).values())
return JsonResponse({'diagnoses': cid_list})
surgery.html (populate select, clear select)
{% block content %}
<form action="surgery/" method="post">
{% csrf_token %}
{{form.as_p}}
<label>diagnosis: </label><select name="cid" id="id_cid"></select>
<br>
<br>
<button type="submit" >Create Procedure</button>
</form>
<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i ) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length 1) === (name '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length 1));
break;
}
}
}
return cookieValue;
}
function removeOptions(selectElement) {
var i, L = selectElement.options.length - 1;
for(i = L; i >= 0; i--) {
selectElement.remove(i);
}
}
function myFunction(value) {
url = '/filter/technique/'
const csrftoken = getCookie('csrftoken');
fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/json'
},
mode: 'same-origin',
body: JSON.stringify({'technique_id': value}),
})
.then((response) => response.json())
.then((data) => {
var diagnosis_select = document.getElementById("id_cid");
removeOptions(diagnosis_select);
for (var i = 0; i<=data.diagnoses.length-1; i ){
var opt = document.createElement('option');
opt.value = data.diagnoses[i]['id'];
opt.innerHTML = data.diagnoses[i]['descricao'];
diagnosis_select.appendChild(opt);
}
})
.catch((error) => {
console.error('Error:', error);
});
}
</script>
{% endblock %}