Opened 5 years ago

Closed 5 years ago

#30907 closed Bug (fixed)

SplitArrayField.has_changed() returns True for unchanged fields when using remove_trailing_nulls.

Reported by: Pavel Dedik Owned by: Pavel Dedik
Component: contrib.postgres Version: dev
Severity: Normal Keywords:
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

This ticket addresses similar issue as in https://code.djangoproject.com/ticket/28950 only for the SplitArrayField

I have the following models and forms:

class Service(models.Model):
    name = pg_fields.CICharField(max_length=100)


class Environment(models.Model):
    service = models.ForeignKey(Service, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    urls = pg_fields.ArrayField(
        models.URLField(max_length=500), null=True, default=list
    )


class EnvironmentForm(forms.ModelForm):
    urls = SplitArrayField(
        forms.URLField(required=False),
        remove_trailing_nulls=True,
        required=False,
        size=2,
    )

    class Meta:
        model = models.Environment
        fields = ["name", "urls"]


ServiceEnvironmentsFormSet = forms.inlineformset_factory(
    models.Service,
    models.Environment,
    form=EnvironmentForm,
    extra=2,
    can_delete=True,
)

class ServiceForm(forms.ModelForm):
    class Meta:
        model = models.Service
        fields = ["name"]

Note the ServiceEnvironmentsFormSet which I'm rendering in a template including the SplitArrayField named urls. When I fill only the name of the Service in the rendered ServiceForm without entering any data to rendered Environment forms, the ServiceEnvironmentsFormSet validation fails. That's because in EnvironmentForm, the name is required, but that's not the problem - this should be ok when using InlineFormSet as each form in the FormSet is optional. So why does it fail? Because Django starts validating forms in InlineFormSet if the form.has_changed() method returns True. And in this case, it always returns True because SplitArrayField.has_changed() returns True in the following cases:

SplitArrayField.has_changed(None, ["", ""])  # returns True even though it shouldn't as ["", ""] means no data in the form has been entered
SplitArrayField.has_changed([], ["", ""])  # returns True - should be False
SplitArrayField.has_changed(["a"], ["a", ""])  # returns True - should be False

Note that I'm using remove_trailing_nulls=True in the SplitArrayField. So the above cases should all evaluate to False I believe.

I already opened PR for this here: https://github.com/django/django/pull/11970

BTW I'm not sure how has_changed() should be evaluated when passing both remove_trailing_nulls=False and required=False to the base_field, as in that case all required fields in the FormSet would have to be filled by the user since has_changed() would always return True even for empty input values.

Change History (7)

comment:1 by Pavel Dedik, 5 years ago

Type: UncategorizedBug

comment:2 by Mariusz Felisiak, 5 years ago

Owner: set to Pavel Dedik
Status: newassigned
Summary: SplitArrayField.has_changed() returns True for unchanged fieldsSplitArrayField.has_changed() returns True for unchanged fields when using remove_trailing_nulls.
Triage Stage: UnreviewedAccepted
Version: 2.2master

I agree we should fix this behavior when remove_trailing_nulls=True.

comment:3 by Mariusz Felisiak, 5 years ago

Patch needs improvement: set

comment:4 by Mariusz Felisiak, 5 years ago

Patch needs improvement: unset
Triage Stage: AcceptedReady for checkin

comment:5 by Mariusz Felisiak <felisiak.mariusz@…>, 5 years ago

In d95b1ddc:

Refs #30907 -- Added more tests for SplitArrayField.has_changed().

comment:6 by Mariusz Felisiak <felisiak.mariusz@…>, 5 years ago

In bcfbb71c:

Refs #30907 -- Added SplitArrayField._remove_trailing_nulls() hook.

comment:7 by Mariusz Felisiak <felisiak.mariusz@…>, 5 years ago

Resolution: fixed
Status: assignedclosed

In 711a7d4:

Fixed #30907 -- Fixed SplitArrayField.has_changed() with removal of empty trailing values.

Note: See TracTickets for help on using tickets.
Back to Top