Formset is creating new instances insted of update

I have this code, the contactForm work well but the socialProofForm just duplicate all db entries at save, instead of update the existing query, someone can help pls, i have 3 days with this bug i think may django bug


# django
from django.utils.translation import gettext_lazy as _
from django.shortcuts import render, redirect, get_object_or_404
from django.forms.models import modelformset_factory
from django.contrib.auth.decorators import login_required

# forms
from ..forms.files import ImageForm
from ..forms.business import BusinessForm, PremiseForm, PositionForm, OpenDaysForm, OpenDaysSForm, BusinessHours, SocialProofForm
from ..forms.general import ContactForm, GeolocationForm


# models
from ..models.business import Business
from ..models.premises import Premise, Position, OpenDays, SocialProof

from ..models.general import Contact


contactForm = modelformset_factory(
    Contact,
    form=ContactForm,
    extra=8,
    max_num=8
)

socialProofForm = modelformset_factory(
    SocialProof,
    form=SocialProofForm,
    extra=0,
    max_num=3
)


def GenerateForms(request, business, premises):

    premisesF = [{}]
    for i, premise in enumerate(premises):

        # multiple other forms

        premisesF[i]['contact'] = contactForm(
            request.POST or None,
            queryset=premise.contact.all(),
            prefix=f'{i}_contact',
        )

        premisesF[i]['proof'] = socialProofForm(
            request.POST or None,
            queryset=premise.social_proof.all(),
            prefix=f'{i}_proof',
        )

    return {
        'premisesF': premisesF
    }


def BusinessAdminView(request, business_id):

    business = get_object_or_404(Business, id=business_id)
    premises = Premise.objects.filter(business=business.pk).order_by('name')

    forms = GenerateForms(request, business, premises)
    premisesF = forms['premisesF']

    if request.POST:
        for i, premiseF in enumerate(premisesF):
            premise = premises[i]
            # multiple other forms

            if premiseF['proof'].is_valid():
                formDirty = True

            for form in premiseF['proof']:

                if form.empty_permitted and not form.has_changed():
                    continue
                # this ever duplicate the existed element with the instance data
                form.save()
                if not premise.social_proof.filter(pk=form.instance.pk).exists():
                    premise.social_proof.add(form.instance)

            if premiseF['contact'].is_valid():
                formDirty = True

                for cont in premiseF['contact']:
                    # all here work well
                    if cont.empty_permitted and not cont.has_changed():
                        continue
                    # this just update the modified element as spected
                    cont.save()
                    if not premise.contact.filter(id=cont.instance.id).exists():
                        premise.contact.add(cont.instance)

    if (formDirty):
        return redirect(request.path)

    return render(request, 'templates/business.html', {
        'premisesF': forms['premisesF'],
    })

View

{{ premise.proof.management_form }}
{% for sp in premise.proof %}
 {% for hidden in sp.hidden_fields %}{{ hidden }}{% endfor %}
 <div class="input-placeholder-textarea" data-placeholder="{{ sp.title.name }}">{{ sp.title }}</div>
 <div class="input-placeholder-textarea" data-placeholder="{{ sp.content.name }}">{{ sp.content }}</div>
{% endfor %}

Forms

class SocialProofForm(ModelForm):
    class Meta:
        model = SocialProof
        fields = ('title', 'content')


class OpenDaysForm(ModelForm):
    class Meta:
        model = OpenDays
        fields = '__all__'

Please post the models involved.

1 Like

This are all models involved, thanks

class SocialProof(models.Model):
    title = models.CharField(
        max_length=100, blank=True
    )
    content = models.CharField(
        max_length=100, blank=True
    )

    def __str__(self):
        return f"SocialProof({self.id}) | Title: {self.title} | Content: {self.content}"
class Platform(models.Model):

    name = models.CharField(
        max_length=100
    )

    url = models.CharField(
        max_length=100
    )

    def __str__(self):
        return f"{self.name} | {self.url}"

class Contact(models.Model):

    type = models.ForeignKey(Platform, related_name='contact_platform', on_delete=models.CASCADE)

    content = models.CharField(
        max_length=100
    )
    
    def __str__(self):
        return f"Contact | Type: {self.type} | content: {self.content}"
type class Premise(models.Model):

    published = models.BooleanField(default=False)

    changed = models.BooleanField(default=False)

    business = models.ForeignKey(
        business.Business, related_name='premise_business',
        on_delete=models.CASCADE
    )

    name = models.CharField(
        max_length=100, blank=True
    )
    
    description = models.CharField(
        max_length=280, blank=True
    )

    social_proof = models.ManyToManyField(
        SocialProof, related_name='premise_social_proof', blank=True
    )

    categories = models.ManyToManyField(
        general.CategoryGeneral, related_name='premise_categories', blank=True
    )

    contact = models.ManyToManyField(
        general.Contact, related_name='premise_contact', blank=True
    )

    position = models.OneToOneField(
        Position, on_delete=models.CASCADE, blank=True, null=True
    )

    location = models.ForeignKey(
        locations.Location, related_name='premise_location',
        on_delete=models.CASCADE, blank=True, null=True
    )

    logo = models.OneToOneField(
        files.Image, related_name='premise_logo',
        on_delete=models.CASCADE, blank=True, null=True
    )
    icon = models.OneToOneField(
        files.Image, related_name='premise_icon',
        on_delete=models.CASCADE, blank=True, null=True
    )
    open_days = models.OneToOneField(
        OpenDays, on_delete=models.CASCADE, blank=True, null=True
    )
    # Images---------------------------------------------------------
    images = models.ManyToManyField(
        files.Image, related_name='premise_images', blank=True
    )
    # Contetnt-------------------------------------------------------

    favorites = models.OneToOneField(
        Favorite,
        on_delete=models.CASCADE, blank=True, null=True
    )
    products = models.ManyToManyField(
        selleables.Product, related_name='premise_products', blank=True
    )
    services = models.ManyToManyField(
        selleables.Service, related_name='premise_services', blank=True
    )
    events = models.ManyToManyField(
        selleables.Event, related_name='premise_events', blank=True
    )


    def delete(self, *args, **kwargs):
        if self.position:
            self.position.delete()
        if self.logo:
            self.logo.delete()
        if self.icon:
            self.icon.delete()
        if self.favorites:
            self.favorites.delete()

        super().delete(*args, **kwargs)

    def __str__(self):
        return f"Premise | Name: {self.name} | Categories: {self.categories}| Description: {self.description}"

You have not included the primary key (id) in your SocialProofForm, and so the posted data has no way of being associated with the existing data. (You can mark it as a hidden field, but it still needs to be part of the form for it to be updated and not added.)

This is what the code show in firefox that is what supposedly makes the line

{% for hidden in sp.hidden_fields %}{{ hidden }}{% endfor %}

Screenshot 2023-10-19 101754

I make your recomended changes but still not working:

class SocialProofForm(ModelForm):
    class Meta:
        model = SocialProof
        fields = ('id', 'title', 'content')
{% for sp in premise.proof %}
 {% for hidden in sp.hidden_fields %}{{ hidden }}{% endfor %}
                    
 {{ sp.id }}

<div class="input-placeholder-textarea" data-placeholder="{{ sp.title.name }}">{{ sp.title }}</div>
<div class="input-placeholder-textarea" data-placeholder="{{ sp.content.name }}">{{ sp.content }}</div>

{% endfor %}

i fix the bug, i has a other form where i have the social_proof but i never make use of that field in the view, possibly interpreted as being part of this form and make that error

class PremiseForm(ModelForm):
    description = forms.CharField(widget=forms.Textarea(attrs={"rows": "5"}))

    class Meta:
        model = Premise
        fields = ('name', 'description', 'categories', 'social_proof') # this  'social_proof' was never used and i delete that

Thanks for take the time to read my messages