0

I am having 2 issues, one if you submit and click back and then submit again it duplicates the instance in the database - in this case Household. In addition it is saving the parent 'Household' without the child 'Applicants' despite me setting min_num=1

can someone point me in the right direction to resolve this issue.

Many thanks in advance

class Application(models.Model):
    name = models.CharField(max_length=100, blank=True, null=True)
    application_no = models.CharField(max_length=100, unique=True, default=create_application_no)
    created_date = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE
    )

class HouseHold(models.Model):
    name = models.CharField(max_length=100)
    application = models.ForeignKey(Application, on_delete=models.CASCADE)
    no_of_dependents = models.PositiveIntegerField(default=0)

class Applicant(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    household = models.ForeignKey("HouseHold", on_delete=models.CASCADE)

forms.py

class ApplicationForm(ModelForm):
    class Meta:
        model = Application
        fields = (
            "name",
        )


class ApplicantForm(ModelForm):
    class Meta:
        model = Applicant
        fields = [
            "household",
            "first_name",
            "last_name"
        ]

class HouseHoldForm(ModelForm):
    class Meta:
        model = HouseHold
        fields = [
            'name',
            'application',
            'no_of_dependents'
        ]

    def __init__(self, application_id=None, *args, **kwargs):
        super(HouseHoldForm, self).__init__(*args, **kwargs)
        self.fields['name'].label = 'House Hold Name'
        if application_id:
            self.fields['application'].initial = application_id
            self.fields['application'].widget = HiddenInput()


ApplicantFormset = inlineformset_factory(
    HouseHold, Applicant, fields=('household', 'first_name', 'last_name'), can_delete=False, extra=1, validate_min=True, min_num=1)

views.py

class HouseHoldCreateView(LoginRequiredMixin, generic.CreateView):
    model = models.HouseHold
    template_name = "households/household_create.html"
    form_class = HouseHoldForm

    def get_parent_model(self):
        application = self.kwargs.get('application_pk')
        return application

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.POST:
            context['application'] = models.HouseHold.objects.filter(application_id=self.kwargs['application_pk']).last()
            context['house_hold_formset'] = ApplicantFormset(self.request.POST, instance=self.object)
        else:
            context['application'] = models.Application.objects.get(id=self.kwargs['application_pk'])
            context['house_hold_formset'] = ApplicantFormset()
        return context

    def get_form_kwargs(self):
        kwargs = super(HouseHoldCreateView, self).get_form_kwargs()
        print(kwargs)
        kwargs['application_id'] = self.kwargs.get('application_pk')
        return kwargs
    
    def form_valid(self, form):
        context = self.get_context_data()
        applicants = context['house_hold_formset']
        with transaction.atomic():
            self.object = form.save()
            if applicants.is_valid():
                applicants.instance = self.object
                applicants.save()
        return super(HouseHoldCreateView, self).form_valid(form)

    def get_success_url(self):
        if 'addMoreApplicants' in self.request.POST:
            return reverse('service:household-create', kwargs={'application_pk': self.object.application.id})
        return reverse('service:household-list', kwargs={'application_pk': self.object.application.id})
4

2 回答 2

1

我有一个类似的问题,我通过将 post() 方法添加到视图来解决它。这个例子是一个 UpdateView 但用法是一样的。(缩进不正确,但这就是stackoverflow的编辑器让我做的,想象所有方法都是右边的4个空格)

class LearnerUpdateView(LearnerProfileMixin, UpdateView):
    model = User
    form_class = UserForm
    formset_class = LearnerFormSet
    template_name = "formset_edit_learner.html"
    success_url = reverse_lazy('home')

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    learner = User.objects.get(learner=self.request.user.learner)
    formset = LearnerFormSet(instance=learner)
    context["learner_formset"] = formset
    return context

def get_object(self, queryset=None):
    user = self.request.user
    return user

def post(self, request, *args, **kwargs):
    self.object = self.get_object()
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    user = User.objects.get(learner=self.get_object().learner)
    formsets = LearnerFormSet(self.request.POST, request.FILES, instance=user)

    if form.is_valid():
        for fs in formsets:
            if fs.is_valid():
                # Messages test start
                messages.success(request, "Profile updated successfully!")
                # Messages test end
                fs.save()
            else:
                messages.error(request, "It didn't save!")
                
        return self.form_valid(form)
    return self.form_invalid(form)

请记住,要正确保存表单集,您还必须在模板中进行一些繁重的工作。我指的是可能会扰乱验证过程的隐藏字段。这是与上面发布的视图相对应的模板:

<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ learner_formset.management_form}}
    {% for form in learner_formset %}
        {% if forloop.first %}
        {% comment %} This makes it so that it doesnt show the annoying DELETE checkbox {% endcomment %}
            {% for field in form.visible_fields %}
                {% if field.name != 'DELETE' %}
                    <label for="{{ field.name }}">{{ field.label|capfirst }}</label>
                    <div id="{{ field.name }}" class="form-group">
                        {{ field }}
                        {{ field.errors.as_ul }}
                    </div>
                {% endif %}
            {% endfor %}
        {% endif %}
        {% for field in form.visible_fields %}
            {% if field.name == 'DELETE' %}
                {{ field.as_hidden }}
            {% else %}
   
                {# Include the hidden fields in the form #}
                {% if forloop.first %}
                    {% for hidden in form.hidden_fields %}
                        {{ hidden }}
                    {% endfor %}
                {% endif %}    
            {% endif %}
        {% endfor %}
    {% endfor %}
<input class="btn btn-success" type="submit" value="Update"/>

附加阅读:

于 2020-11-25T13:37:37.467 回答
0

受 Beikini 的启发,我使用 create View 解决了这个问题

class HouseHoldCreateView(LoginRequiredMixin, generic.CreateView):
    model = HouseHold
    template_name = "households/household_create3.html"
    form_class = HouseHoldForm

    def get_parent_model(self):
        application = self.kwargs.get('application_pk')
        return application

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.POST:
            context['application'] = HouseHold.objects.filter(
                application_id=self.kwargs['application_pk']).last()
            context['house_hold_formset'] = ApplicantFormset(self.request.POST)
        else:
            context['application'] = Application.objects.get(id=self.kwargs['application_pk'])
            context['house_hold_formset'] = ApplicantFormset()
        return context

    def get_form_kwargs(self):
        kwargs = super(HouseHoldCreateView, self).get_form_kwargs()
        kwargs['application_id'] = self.kwargs.get('application_pk')
        return kwargs

    def form_valid(self, form):
        context = self.get_context_data()
        applicants = context['house_hold_formset']
        application_id = self.kwargs['application_pk']
        household_form = self.get_form()

        if form.is_valid() and applicants.is_valid():
            with transaction.atomic():
                self.object = form.save()
                applicants.instance = self.object
                applicants.save()
                messages.success(self.request, 'Applicant saved successfully')
                return super(HouseHoldCreateView, self).form_valid(form)
        else:
            messages.error(self.request, 'please add an applicant to the household')
            return self.form_invalid(form)

    def get_success_url(self):
        return reverse('service:household-list', kwargs={'application_pk': self.object.application.id})
于 2020-11-27T00:19:43.930 回答