UpdateView creates new items in a Formset, does not update them

1

Two months ago I started using Django. Right now I have the CBV of a form with two elements, a Parent Model and a Child Model that can be multiple. I have functional part of Create, Detail, List and Delete. I have the problem when editing with the form of the Father model with multiple Formsets of the Son Model, because when it saves the new information, it creates new elements instead of updating them (For example, if I edit 3 instances of the Son , creates 3 objects with the new information and keeps the 3 previous children unchanged).

When creating the new children objects, I can see that the information is correct, that it keeps them as children of the Father, etc. The information is correct, but some error in my code does not relate the children of the UpdateView with the existing ones, that's why it creates the new objects.

This is my code ( Entrada is the Father, with multiple instincts of BalaMateriesPrimeres ):

models.py

class Entrada(models.Model):
    class Meta:
        verbose_name_plural = 'Entrades'

    data_hora = models.DateTimeField(auto_now=True)
    num_factura = models.CharField(max_length=20, blank=True)
    cost_total = models.DecimalField(max_digits=10, decimal_places=3, null=True, blank=True)
    quilos_total = models.PositiveIntegerField(blank=True, null=True)
    descripcio = models.TextField(blank=True, null=True)
    observacions = models.TextField(blank=True, null=True)
    proveidor = models.ForeignKey(Proveidor, related_name='entrades', on_delete=models.CASCADE)

    # Metodos varios


class BalaMateriesPrimeres(models.Model):
    class Meta:
        verbose_name_plural = 'Bales de Materies Primeres'

    magatzem = models.CharField(max_length=20, blank=True)
    quilos = models.DecimalField(max_digits=6, decimal_places=2)
    cost_unitari = models.DecimalField(max_digits=8, decimal_places=4)
    cost_material = models.DecimalField(max_digits=8, decimal_places=4)
    observacions = models.TextField(blank=True)
    SI = 's'
    NO = 'n'
    DESCONEGUT = 'd'
    PREASSIGNADA_CHOICES = (
        (SI, 'Sí'),
        (NO, 'No'),
        (DESCONEGUT, 'Desconegut'),
    )
    pre_assignada = models.CharField(
        max_length=1,
        choices=PREASSIGNADA_CHOICES,
        default=DESCONEGUT,
    )
    barcode = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
    SI = 's'
    NO = 'n'
    EN_PROCES = 'p'
    CONSUMIDA_CHOICES = (
        (SI, 'Sí'),
        (NO, 'No'),
        (EN_PROCES, 'En procés'),
    )

    consumida = models.CharField(
        max_length=1,
        choices=CONSUMIDA_CHOICES,
        default=NO,
    )
    material = models.ForeignKey('Material', related_name='materials', on_delete=models.PROTECT)
    fabrica = models.ForeignKey('Fabrica', related_name='bales_materials', on_delete=models.PROTECT, blank=True, null=True)
    num_entrada = models.ForeignKey(
        Entrada, related_name='bales_materies_primeres', on_delete=models.CASCADE)

    # Metodos varios

forms.py

class EntradaForm(ModelForm):
    class Meta:
        model = Entrada
        exclude = ()


class BalaMateriesPrimeresForm(ModelForm):
    class Meta:
        model = BalaMateriesPrimeres
        fields = ['quilos', 'material', 'cost_unitari', 'cost_material']

BalaMateriesPrimeresFormSet = inlineformset_factory(Entrada, BalaMateriesPrimeres, form=BalaMateriesPrimeresForm, can_delete=True, extra=1)

views.py

class EntradaUpdateView(LoginRequiredMixin, UpdateView):
    model = Entrada
    fields = [...]
    template_name_suffix = '_update'

    def get_context_data(self, **kwargs):
        data = super(EntradaUpdateView, self).get_context_data(**kwargs)

        # Utilizo esto para traerme los elementos hijo. Puede ser que esto sea lo que está mal?
        if self.request.POST:
            data['bala_form'] = BalaMateriesPrimeresFormSet(self.request.POST)
        else:
            data['bala_form'] = BalaMateriesPrimeres.objects.filter(num_entrada=self.kwargs['pk'])
        return data

    def form_valid(self, form):

        self.object = self.get_object()
        form_class = self.get_form_class()
        form = self.get_form(form_class)

        qs = BalaMateriesPrimeres.objects.filter(num_entrada=self.get_object())
        formsets = BalaMateriesPrimeresFormSet(self.request.POST, queryset=qs)

        if form.is_valid():
            form.save()
            if formsets.is_valid():
                instances = formsets.save(commit=False)
                for instance in instances:
                    instance.instance = self.object
                    instance.num_entrada = Entrada.objects.get(pk=self.object.id)
                    instance.save()

        return super(EntradaUpdateView, self).form_valid(form)

With this I get to save the elements as new bullets (If you load 3 bullets, create 3 new bullets without relation to the previous ones). I've been testing for 2 days and I do not know how to do it, so I'd appreciate it if someone points me to what is failing or to some functional example with multiple Formsets.

    
asked by DavidMM 16.05.2018 в 10:33
source

1 answer

0

I already found the problem: In get_context_data I saved the Formset information but I did not pass it to the template in an appropriate way. I leave here the correct way in case someone needs help on how to modify a Father with multiple Sons:

class PadreUpdateView(LoginRequiredMixin, UpdateView):
    model = Padre
    form_class = PadreForm
    template_name_suffix = '_update'

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        hijo_form = HijoFormSet(instance = self.object)
        return self.render_to_response(self.get_context_data(form = form, hijo_form = hijo_form ))

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        hijo_form = HijoFormSet(self.request.POST, instance=self.object)

        if (form.is_valid() and hijo_form.is_valid()):
            return self.form_valid(form, bala_form)
        return self.form_invalid(form, bala_form)

    def form_valid(self, form, bala_form):
        self.object = form.save()
        hijo_form.instance = self.object
        hijo_form.save()

        return HttpResponseRedirect(self.get_success_url())

Attention: VERY important not to forget to place in the template in the iteration of the son_form, the hijo.id. Otherwise, it will give us form_invalid:

{% for hijo in hijo_form %}
{{ hijo.id }}
....
{% endfor %}
    
answered by 16.05.2018 в 13:50