django-select2 icon indicating copy to clipboard operation
django-select2 copied to clipboard

Does not work with `to_field`

Open rez0n opened this issue 4 years ago • 2 comments

Hi @codingjoe, I faced an issue using to_field option in the model field. Form can't be validated at all.

class ModelA(models.Model):
    id = models.AutoField(primary_key=True)
    slug = models.CharField(max_length=4, unique=True)


class ModelB(models.Model):
    id = models.AutoField(primary_key=True)
    a_slug = models.ForeignKey(ModelA, verbose_name=_("Select A"), on_delete=models.CASCADE, to_field='slug')
    note = models.CharField(_("Note"), max_length=50)


class ModelAPickWidget(s2forms.ModelSelect2Widget):
    model = ModelA
    search_fields = [
        "slug__icontains",
    ]

    def label_from_instance(self, obj):
        return str(f'{obj.slug}')


class ModelBForm(ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # self.fields['a_slug'].widget = ModelAPickWidget()
        # self.fields['a_slug'].widget.attrs['class'] = 'form-control'
        # self.fields['a_slug'].to_field_name = 'slug'
        # self.fields['a_slug'].queryset = ModelA.objects.all()

    class Meta:
        model = ModelB
        fields = ['a_slug', 'note', ]
        widgets = {
            "a_slug": ModelAPickWidget(attrs={
                'class': 'form-control',
            }),
            "note": widgets.TextInput(attrs={
                'class': 'form-control',
            }),
        }

If I going to disable ModelAPickWidget to use default Django's select widget it works and saves the form.

There is some debugging information.

  1. ModelSelect2Mixin in the optgroups method value is always pk
  2. ModelSelect2Mixin in the optgroups line
query = Q(**{"%s__in" % field_name: selected_choices})
print(query)
>>> (AND: ('slug__in', {'196'}))
  1. I tried to change
query = Q(**{"%s__in" % field_name: selected_choices})

to

query = Q(**{"%s__in" % 'pk': selected_choices})

And I got some success, form get failed validation on first submission but saves if I re-submit it again.

Form validation error raised by Django's ModelChoiceField in the to_python method

Select a valid choice. That choice is not one of the available choices.

because it gets pk value instead slug.

rez0n avatar Oct 30 '21 15:10 rez0n

I performed a lot experiments with the optgroups method but have no success, by some reason first form submission get failed validation and validates successfully on the next submit.

A very dirty hack to make it works (nothing related to select2 module, just maybe it will helps to faster understand core of the issue.

class ModelChoiceField2(ChoiceField):

    def to_python(self, value):
        if value in self.empty_values:
            return None
        try:
            key = self.to_field_name or 'pk'
            if isinstance(value, self.queryset.model):
                value = getattr(value, key)
            value = self.queryset.get(**{'pk': value})   <<<<< HERE changed key to 'pk'
            value = value
        except (ValueError, TypeError, self.queryset.model.DoesNotExist):
            raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
        return value

    def valid_value(self, value):
        # Override validation to check is selected value exists in the db
        text_value = str(value)
        if not self.queryset.model(slug=text_value):
            return False
        
        return True

class ModelBForm(ModelForm):
    a_slug = ModelChoiceField2()
...

rez0n avatar Oct 30 '21 15:10 rez0n

I recently fixed the same issue in Django's autocomplete field. I'd be delighted if you could try to backport this patch to this repo as well. I am pretty overwhelmed with work right now, so I'd appreciate the help.

You can find my Django commit here https://github.com/django/django/commit/3071660acfbdf4b5c59457c8e9dc345d5e8894c5

codingjoe avatar Nov 02 '21 10:11 codingjoe