Search code examples
pythondjangoformset

django-dynamic-formset issue when delete


When I'm trying to remove a row, the formset send me the following error [{}, {}, {}, {'id': ['This field is required']}]. If I change the following parameter can_delete=True inside the modelformset_factory, I can delete object but rows stays display even after I pressed the remove button. I tried to add {% if form.instance.pk %}{{ form.DELETE }}{% endif %} in the template, it didn't change anything.

I am using django 2.2 and django-dynamic-formset, the country_fr/en come from modeltranslation.

Views

@login_required
def view_countries(request):
    CountryFormSet = modelformset_factory(
        Country,
        fields=('country_fr', 'country_en'),
        formset=BaseCountryFormSet,
        can_delete=True)
    if request.method == 'POST':
        formset = CountryFormSet(request.POST)
        if formset.is_valid():
            formset.save()
    else:
        formset = CountryFormSet()
    context = {
        'formset': formset,
        'menu': 'cards',
        'menu_cards': 'countries',
        'model': _('countries'),
        'fields': [_('countries')+' [fr]', _('countries')+' [en]'],
    }
    return render(request, 'cards/index_formset.html', context)

Html

<form method="post" class="mt-2">
  {% csrf_token %}
  {{ formset.management_form }}

  {% for form in formset %}
  <div id="form" class="row_formset d-flex text-center mb-1">
    {% if form.instance.pk %}{{ form.DELETE }}{% endif %}
    {% for field in form.hidden_fields %}
    <div class="invisible">
      {{ field }}
    </div>
    {% endfor %}
    {% for field in form.visible_fields %}
    <div class="flex-fill field">
      {{ field }}
    </div>
    {% endfor %}
  </div>
  {% endfor %}

  <div class="d-flex mt-2 justify-content-center">
    <input type="submit" value="{% trans 'submit'|capfirst %}" class="btn btn-primary"/>
  </div>
</form>

Forms

class BaseCountryFormSet(BaseModelFormSet):
    def clean(self):
        if any(self.errors):
            raise forms.ValidationError(
                _('Errors : ')+f'{self.errors}'+'.',
                code='unknow_error'
            )
        countries_fr = []
        countries_en = []
        duplicates = False
        for form in self.forms:
            if form.cleaned_data:
                country_fr = form.cleaned_data['country_fr']
                country_en = form.cleaned_data['country_en']

                # Check that no row have the same country_fr or country_en
                if country_fr and country_en:
                    if country_fr in countries_fr:
                        duplicates = True
                    countries_fr.append(country_fr)

                    if country_en in countries_en:
                        duplicates = True
                    countries_en.append(country_en)

                if duplicates:
                    raise forms.ValidationError(
                        _('Some entries are duplicated.'),
                        code='duplicate_row'
                    )

                # Check that all row have both country_fr and country_en
                if country_en and not country_fr:
                    raise forms.ValidationError(
                        _('Some french entries are missing.'),
                        code='missing_country_fr'
                    )
                elif country_fr and not country_en:
                    raise forms.ValidationError(
                        _('Some english entries are missing.'),
                        code='missing_country_en'
                    )

Models

class Country(models.Model):
    country = models.CharField(_('country'), max_length=100)
    slug = models.SlugField(editable=False, max_length=100)

    def save(self, *args, **kwargs):
        self.slug_fr = slugify(self.country_fr, allow_unicode=False)
        self.slug_en = slugify(self.country_en, allow_unicode=False)
        super().save(*args, **kwargs)

Solution

  • So the problem come from bootstrap, the d-flex of each form of the formset contain the attribut !important wich override the display: none; of the django-dynamic-formset-script.

    I made my own css class to replace d-flex. I kept can_delete=True, it's obviously useful; and I removed {% if form.instance.pk %}{{ form.DELETE }}{% endif %}.