Обновление и создание существующих данных в Django

Мне нужно обновлять и, при необходимости, создавать элементы в представлении обновления Django. В принципе, у меня есть форма, где я даю пользователю возможность обновить строку или вставить одну или несколько новых строк. Проблема в том, что у меня возникают проблемы с обновлением "старых" строк. Если я обновляю существующую строку, то создается новая. Здесь я размещаю некоторый код:

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'}),
        }

шаблон

{% 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 %}

Форма обновляется правильно, но проблема в части, касающейся Целей обучения. Есть предложения?

Проблема здесь:

learning_objective, created = LearningObjective.objects.get_or_create(name=lo, mission_id=mission.id)

В частности, часть mission_id=mission.id. Если вы хотите выполнить поиск по ForeignKey, вам нужны два символа подчеркивания. Таким образом, запрос не находит LearningObjective, поэтому он всегда создает новый. Но это и не нужно, так как вы уже отфильтровали learning_objectives по миссии (и там это было сделано с правильным синтаксисом).

Тогда решение заключается в следующем:

learning_objective, created = LearningObjective.objects.get_or_create(name=lo)

if not created:
    learning_objective.name = lo
    learning_objective.save()

Решение, однако, можно сделать гораздо проще с помощью update_or_create. Это то же самое, что делаете вы, но в одной строке вместо 4.

learning_objective, created = LearningObjective.objects.update_or_create(name=lo, defaults={'name': lo})
Вернуться на верх