I have a Django app that among other things wants the user to enter a date of birth or their age. Upon the entry of one, the other should be calculated and when the form is saved, the database should be updated with both values. Unfortunately it does not appear that the modified data is seen when the form is submitted. If the birthday is entered and the age calculated, clean() will indicate that age is None. When the age is entered and the birthday calculated everything seems to work correctly. I am assuming that it has something to do with changing the disabled property, but have been unable to find anything to support this. I have also seen similar results with other fields where I change the disabled flag.
Excerpt from models.py:
class Patient(models.Model):
birthday = models.DateField(_('Birthday'), blank=True)
age = models.IntegerField(_('Age'), blank=True)
Excerpt from forms.py
class PatientCreateForm(forms.ModelForm):
birthday = forms.DateField(widget=DateInput(attrs={'type': 'date'}), required=True)
age = forms.IntegerField(required=True)
class Meta:
model = Patient
fields = [
'birthday',
'age',
Excerpt from views.py
def createpatient(request):
if not request.user.is_authenticated:
logger.debug(f'no user is logged in')
login_url = reverse("login")
next_url = reverse("create-patient")
redirect_url = f'{login_url}?next={next_url}'
logger.debug(f'redirect to {redirect_url}')
return redirect(redirect_url)
# value of patient_id is calculated here
if request.method == 'POST':
form = PatientCreateForm(request.POST, initial={'patient_id': patient_id})
if form.is_valid():
form.save()
messages.success(request, _(f'Patient {form.cleaned_data.get('full_name')} has been created.'))
return redirect('patient-list')
else:
form = PatientCreateForm(initial={'patient_id': patient_id})
return render(request, 'patients/patient_form.html', {'form': form})
Excerpt from patient_form.html
{% extends 'patients/base.html' %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block content %}
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.7/dist/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
// event handler to update age based on birthday
$("#id_birthday").on('focusout', function() {
if($("#id_birthday").val()) {
var birthday = new Date($("#id_birthday").val());
var today = new Date();
var age = Math.floor((today - birthday) / (365.25 * 24 * 60 * 60 * 1000));
$("#id_age").val(age);
$("#id_age").prop('disabled', true);
}
else {
$("#id_age").prop('disabled', false);
}
});
// event handler to set birthday based on age, only if no birthday entered
$("#id_age").on('focusout', function() {
var bd = $("#id_birthday").val();
if(!Date.parse(bd)) {
if($("#id_age").val()) {
var age = $("#id_age").val();
var birthyear = new Date().getFullYear() - age;
var birthday = new Date(birthyear, 0, 1);
$("#id_birthday").val(birthyear + "-01-01");
}
}
});
});
</script>
<div class="content-section">
<form method="POST">
{% csrf_token %}
{{ form.non_field_errors }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">{% trans "Patient Information" %}</legend>
<div class="form-row">
<div class="form-group col-md-6 mb-0">
{{ form.birthday|as_crispy_field }}
</div>
<div class="form-group col-md-6 mb-0">
{{ form.age|as_crispy_field }}
</div>
</div>
{{ form.patient_id|as_crispy_field }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">{% trans "Create" %}</button>
<a class="btn btn-outline-danger" onclick="window.history.back()">{% trans "Cancel" %}</a>
</div>
</form>
</div>
{% endblock content %}
What I expect in this particular case: If a birthday is entered, the age should be calculated and the user should not be able to enter an age. If the birthday is not entered, the user should be able to enter and age and the birthday should be calculated. The birthday field should not be disabled.
In the other cases I mentioned in passing I have a field that defaults to 0. If a value is entered (e.g. 1) to other fields should be enabled (i.e. disable = false) and values entered. Here the fields get enabled correctly, but any data entered in the fields is ignored.
Can you try this? I'm not very good with JS, it may not work correctly.
.prop('disabled', true)
with .prop('readonly', true)
for the age field.clean
method of the form to handle either birthday or age. Remove the required=True
in Form. If either field
can calculate the other, you should not require both in the form
declaration.