Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django ArrayField rendered using SelectMultiple not showing the selected optons as 'selected'

I am trying to render postgres Arrayfield values using SelectMultiple widget like this

In models.py:

SLOT_CHOICES = (('A','Slot A'),('B','Slot B'),('C','Slot C'),('D','Slot D'),('E','Slot E'),('F','Slot F'),('G','Slot G'),('H','Slot H'),('P','Slot P'),('Q','Slot Q'),('R','Slot R'),('S','Slot S'),('T','Slot T'),)
core_slots = ArrayField(models.CharField(max_length=1,choices=SLOT_CHOICES),blank=True,null=True)

In forms.py:

    self.fields['core_slots'].widget = forms.SelectMultiple(attrs={
            'placeholder': 'Choose slots',
            'class': 'multi-select-input',
        },choices=self.Meta.model.SLOT_CHOICES)

If I select a single option and submit and then try to get the filled form using 'instance' then it works fine and shows the previously selected option as 'selected'. But if I select more than one option, then on submitting all selected values get correctly inserted into database, but if I try to get the filled form using 'instance' then it doesn't show any option as selected.

This problem doesn't happen for ManyToManyField.

In models.py:

past_courses = models.ManyToManyField(Course,blank=True)

In forms.py:

    self.fields['past_courses'].queryset = Course.objects.filter(~Q(dept=self.instance.dept)).order_by('name')
    self.fields['past_courses'].widget.attrs.update({
            'placeholder': 'Choose past courses',
            'class': 'multi-select-input',
        })

This one works fine. Only ArrayField rendered using SelectMultiple widget has problem. I want to display submitted options in template, is there any way to fix this or is there any alternate way to display selected options in template.

like image 469
Devika Sudheer Avatar asked Oct 16 '25 15:10

Devika Sudheer


2 Answers

What I found was that this seems to be an issue with the fact that the SelectMultiple widget uses ChoiceWidget which has a format_value method that handles multiple selections incorrectly. At least this is in Django 1.11. The format_value does the following:

def format_value(self, value):
    """Return selected values as a list."""
    if value is None and self.allow_multiple_selected:
        return []
    if not isinstance(value, (tuple, list)):
        value = [value]
    return [force_text(v) if v is not None else '' for v in value]

Which as you can see says that it is supposed to return the selected values as a list. But the code that runs if it is not a tuple or list is wrong. It just essentially makes the items in the value be a single entry in the list. So when you have multiple items selected like 'A', 'B', and 'C' - they come through to this format_value function as 'A, B, C' - and this really just makes it be ['A, B, C'] - which is wrong. It needs to be ['A', 'B', 'C']. So I just put in code to override this function with the following:

def format_value(self, value):
    """Return selected values as a list."""
    if value is None and self.allow_multiple_selected:
        return []
    if not isinstance(value, (tuple, list)):
        # This means it should be a comma delimited list of items so parse it
        value = value.split(',')

    return [force_text(v) if v is not None else '' for v in value]

This seems to work fine for me now. I tested on Django 1.11 for picking a single item, multiple items, and NO items and everything saved to the DB properly and showed up on the Django Admin fine as well. Hope this helps.

like image 117
Steve Avatar answered Oct 18 '25 14:10

Steve


Had the same problem, and this is basically the same answer as above, but with a drop in replacement widget.

from django.forms import CheckboxSelectMultiple


class ArrayFieldCheckboxSelectMultiple(CheckboxSelectMultiple):

    def format_value(self, value):
        """Return selected values as a list."""
        if value is None and self.allow_multiple_selected:
            return []
        elif self.allow_multiple_selected:
            value = [v for v in value.split(",")]

        if not isinstance(value, (tuple, list)):
            value = [value]
            
        results = [str(v) if v is not None else '' for v in value]
        return results
like image 29
monkut Avatar answered Oct 18 '25 14:10

monkut



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!