Reputation: 418
I need to update and, if needed, create elements in a Django update view. Basically, I have a form where I am giving the user the chance of updating a row or inserting one or more new rows. The problem is that I am having issues in updating the "old" rows. If I update an existing row, it creates a new one. Here I post some code:
views.py
def edit_flight_mission(request, pk):
mission = Mission.objects.get(id=pk)
form = EditMissionForm(request.POST or None, instance=mission)
learning_objectives = LearningObjective.objects.filter(mission_id=mission)
context = {
'mission': mission,
'form': form,
'learning_objectives': learning_objectives,
}
if request.method == 'POST':
learning_obj = request.POST.getlist('learning_obj')
solo_flight = request.POST.get('solo_flight')
if form.is_valid():
mission_obj = form.save()
if solo_flight == 'solo_flight':
mission_obj.solo_flight = True
mission_obj.save()
for lo in learning_obj:
learning_objective, created = LearningObjective.objects.get_or_create(name=lo, mission_id=mission.id)
if not created:
learning_objective.name = lo
learning_objective.save()
return render(request, 'user/edit_flight_mission.html', context)
models.py
class Mission(models.Model):
name = models.CharField(max_length=200)
duration_dual = models.DurationField(blank=True, null=True)
duration_solo = models.DurationField(blank=True, null=True)
training_course = models.ForeignKey(
TrainingCourse, on_delete=models.CASCADE)
note = models.TextField(null=True, blank=True)
solo_flight = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class LearningObjective(models.Model):
name = models.CharField(max_length=300)
mission = models.ForeignKey(Mission, on_delete=models.CASCADE, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
forms.py
class EditMissionForm(forms.ModelForm):
class Meta:
model = Mission
fields = ('name', 'duration_dual', 'duration_solo', 'training_course')
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter Mission Name'}),
'duration_dual': forms.TextInput(attrs={'class':'form-control', 'placeholder': 'Duration as HH:MM:SS'}),
'duration_solo': forms.TextInput(attrs={'class':'form-control', 'placeholder': 'Duration as HH:MM:SS'}),
'training_course': forms.Select(attrs={'class': 'form-control'}),
}
template
{% extends "base.html" %}
{% block head_title %}
Edit Flight Mission {{mission.id}}
{% endblock head_title %}
{% block title %}
Edit Flight Mission {{mission.id}}
{% endblock title%}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<div class="card-body">
<div class="form-group">
{{form.as_p}}
</div>
<div class="form-group">
<div id="inputFormRow">
<label>Learning Objective</label>
{% for lo in learning_objectives %}
<div class="input-group mb-3">
<input type="text" value="{{lo.name}}" class="form-control" name="learning_obj" placeholder="Learning Objective">
<div class="input-group-append">
</div>
</div>
{% endfor %}
<div id="newRow"></div>
<div class="form group">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="solo_flight" value="solo_flight" id="flexCheckDefault">
<label class="form-check-label" for="flexCheckDefault">
Solo Flight
</label>
</div>
</div>
<button id="addRow" type="button" class="btn btn-primary mb-3">Add Learning Objective</button>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary btn-block">
Add New Mission
</button>
</div>
</form>
{% endblock content %}
{% block custom_js %}
<script type="text/javascript">
// add row
$("#addRow").click(function () {
var html = '';
html += '<div id="inputFormRow">';
html += '<div class="input-group mb-3">'
html += '<input type="text" class="form-control" name="learning_obj" placeholder="Learning Objective">'
html += '<div class="input-group-append">'
html += '<button class="btn btn-danger" type="button" id="remove">Remove</button>'
html += '</div></div>'
$('#newRow').append(html);
});
// remove row
$(document).on('click', '#remove', function () {
$(this).closest('#inputFormRow').remove();
});
</script>
{% endblock custom_js %}
The form is updating correctly but the problem is with the part concerning the Learning Objectives basically. Any suggestion?
Upvotes: 1
Views: 1690
Reputation: 2880
The problem is here:
learning_objective, created = LearningObjective.objects.get_or_create(name=lo, mission_id=mission.id)
Specifically, the mission_id=mission.id
part. If you want to do a lookup to a ForeignKey, you need two underscores. Therefore, the query is not finding the LearningObjective, thus it is always creating a new one. But it's not even necessary, since you've already filtered learning_objectives
by mission (and there, it was done with the correct syntax).
The solution, then is to do this:
learning_objective, created = LearningObjective.objects.get_or_create(name=lo)
if not created:
learning_objective.name = lo
learning_objective.save()
The solution, though can be done much easier with update_or_create. This is the same as what you're doing, but in one line instead of 4.
learning_objective, created = LearningObjective.objects.update_or_create(name=lo, defaults={'name': lo})
Edit
I think the syntax here is actually not correct. Change it as follows:
# Change this,
# learning_objectives = LearningObjective.objects.filter(mission_id=mission)
# To this:
learning_objectives = LearningObjective.objects.filter(mission=mission)
Edit 2
I'm not sure if this problem is what's causing the learning_objectives not to save, but I now see another error in the html. You can not have a form within another form. The {{ form.as_p }}
is creating another <form>
tag within the one you already have. So the form is validating because all the fields of the {{ form.as_p }}
are there, but those are for the Mission object. Are the other fields even being submitted? Check by print(request.POST)
. I'm guessing that it will not contain the name field for the learning_obj.
Possible Solutions:
{{ form }}
manually, so that the <form>
tags are not there. When you submit, all inputs with names will be submitted.Upvotes: 2