1

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?

1
  • Instead of using get_or_create, try and filter the objects if it doesn't exist, create a new one. Commented Feb 26, 2022 at 14:13

1 Answer 1

2

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:

  1. Create a form, but not a ModelForm, that has everything you want to save from the two different models.
  2. Render the {{ form }} manually, so that the <form> tags are not there. When you submit, all inputs with names will be submitted.
Sign up to request clarification or add additional context in comments.

6 Comments

Ok this solution make totally sense and should work but... I really don't know why the data are correctly printed if I try to do so, but they're not saved. As I read in the documentation, the save() is not needed in update_or_create but I can't understand why your solution is not working.
@GiorgioScarso, I've edited my answer. I made an error in my original answer. I hope this helps.
Ok, it looks like everything is working but not saving in the database. Is there any chance that maybe I need to save this update somewhere?
No. update_or_create should not require a save(). Perhaps put in some print() statements in your view at different locations to see what is happening.
I tried and it prints me the right entry but, after that, the database keeps the old record in fact. The database is hit because I can see that the updated_at field is updated with the time of the new entry but that learning_objective name field is not updated. I think it's something around it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.