Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ChoiceField metadata does not support grouped choices. #3101

Closed
maxpeterson opened this issue Jul 1, 2015 · 13 comments
Closed

ChoiceField metadata does not support grouped choices. #3101

maxpeterson opened this issue Jul 1, 2015 · 13 comments
Labels

Comments

@maxpeterson
Copy link
Contributor

If a serialiser has a ChoiceField with grouped choices:

TYPE_CHOICES = (
        ('Default', (
            (1, 'Option 1'),
            (2, 'Option 2'),
            (3, 'Option 3'))),
        (4, 'Option 4'))

The OPTIONS metadata for the field includes the choices:

[
    {
        "value": "Default",
        "display_name": "((1, 'Option 1'), (2, 'Option 2'), (3, 'Option 3'))"
    },
    {
        "value": 4,
        "display_name": "Option 4"
    }
]

I am not certain what the behaviour should be, but something including the nested choices would be useful:

[
    {
        "choices": [
            {
                "value": 1,
                "display_name": "Option 1"
            },
            {
                "value": 2,
                "display_name": "Option 2"
            },
            {
                "value": 3,
                "display_name": "Option 3"
            }
        ],
        "display_name": "Default"
    },
    {
        "value": 4,
        "display_name": "Option 4"
    }
]

If this is not appropriate then a flat list of choices might be appropriate.

[
    {
        "value": 1,
        "display_name": "Option 1"
    },
    {
        "value": 2,
        "display_name": "Option 2"
    },
    {
        "value": 3,
        "display_name": "Option 3"
    },
    {
        "value": 4,
        "display_name": "Option 4"
    }
]
@tomchristie tomchristie changed the title ChoiceField metadata for grouped choices does not include all choice values ChoiceField metadata does not support grouped choices. Jul 2, 2015
@tomchristie tomchristie added the Bug label Jul 2, 2015
@tomchristie
Copy link
Member

The flat list of choices would be my preferred option here.

@maxpeterson
Copy link
Contributor Author

@tomchristie I was hoping you would go for the hierarchical one as I need the hierarchy in an application consuming the api.

If the choices are flat, then how would you recommend we get the hierarchy from the api. My first idea would be to extend SimpleMetadata globally (or on my view) to include an extra attribute for the field.

@tomchristie
Copy link
Member

My first idea would be to extend SimpleMetadata globally (or on my view) to include an extra attribute for the field.

Exactly so, yes.

@maxpeterson
Copy link
Contributor Author

Related to #1533

maxpeterson added a commit to maxpeterson/django-rest-framework that referenced this issue Jul 3, 2015
@xordoquy xordoquy added this to the 3.1.4 Release milestone Jul 8, 2015
@xordoquy xordoquy modified the milestones: 3.1.4 Release, 3.1.5 Release Jul 16, 2015
@ameyc ameyc mentioned this issue Jul 22, 2015
3 tasks
@tomchristie
Copy link
Member

Rolling this into #1533.

@tomchristie tomchristie removed this from the 3.1.5 Release milestone Jul 23, 2015
@wernerhp
Copy link

@maxpeterson @tomchristie I also need the nested/grouped representation on to be returned by the meta for an app. Can you guys perhaps link me to an example of hot to do this?

@maxpeterson
Copy link
Contributor Author

@wernerhp I have achieved this by adding an additional grouped_choices attribute to the meta. I am afraid the code is in a private repo so hopefully these "snippets" will help:

def get_choice(choice_value, choice_name):
    if isinstance(choice_name, (list, tuple)):
        return {
            'choices': [get_choice(*choice) for choice in choice_name],
            'display_name': force_text(choice_value, strings_only=True)
        }
    return {
        'value': choice_value,
        'display_name': force_text(choice_name, strings_only=True)
    }


def grouped_choices(choices_list):
    return [get_choice(*choice) for choice in choices_list]


class GroupedChoiceMetadata(SimpleMetadata):
    def get_field_info(self, field):
        field_info = super().get_field_info(field)

        if not field_info.get('read_only') and hasattr(field, 'choices_list'):
            field_info['grouped_choices'] = grouped_choices(field.choices_list)

        return field_info

@wernerhp
Copy link

wernerhp commented Sep 23, 2016

@maxpeterson Thanks for the snippet. I'm still battling a bit.

choices_list doesn't seem to be a standard attribute of serializers.ChoiceField.
Would you minding including a snippet from your Serializer, Model and your nested/grouped MEDIA_CHOICES data structure.

Working with the Django example

REST_FRAMEWORK = {
...
    'DEFAULT_METADATA_CLASS': 'custom.metadata.GroupedChoiceMetadata',
...
}
class Media(models.Model):
    MEDIA_CHOICES = (
        ('Audio', (
                ('vinyl', 'Vinyl'),
                ('cd', 'CD'),
            )
        ),
        ('Video', (
                ('vhs', 'VHS Tape'),
                ('dvd', 'DVD'),
            )
        ),
        ('unknown', 'Unknown'),
    )

    type = models.CharField(max_length=255, choices=MEDIA_CHOICES)
class MediaSerializer(serializers.ModelSerializer):
    class Meta:
        fields = [
            'type',
        ]
        model = Media

printing out field.choices in GroupedChoiceMetadata.get_field_info yields an OrderedDict
OrderedDict([('vinyl', 'Vinyl'), ('cd', 'CD'), ('vhs', 'VHS Tape'), ('dvd', 'DVD'), ('unknown', 'Unknown')]) so the structure has already been lost/flattened.
DRF 3.4.0
Thanks in advance

@maxpeterson
Copy link
Contributor Author

@wernerhp try using field.grouped_choices - Sorry about that choices_list was based on an earlier version I used

@maxpeterson
Copy link
Contributor Author

Here is the PR #3225 grouped_choices was added and my original one #3108 (where I suggested choices_list)

@maxpeterson
Copy link
Contributor Author

To clarify try ...

def get_choice(choice_value, choice_name):
    if isinstance(choice_name, (list, tuple)):
        return {
            'choices': [get_choice(*choice) for choice in choice_name],
            'display_name': force_text(choice_value, strings_only=True)
        }
    return {
        'value': choice_value,
        'display_name': force_text(choice_name, strings_only=True)
    }


def grouped_choices(choices_list):
    return [get_choice(*choice) for choice in choices_list]


class GroupedChoiceMetadata(SimpleMetadata):
    def get_field_info(self, field):
        field_info = super().get_field_info(field)

        if not field_info.get('read_only') and hasattr(field, 'grouped_choices'):
            field_info['grouped_choices'] = grouped_choices(field.grouped_choices)

        return field_info

@wernerhp
Copy link

After some effort I found that group_choices is actually a property on ChoiceField
This seems to do the trick

class GroupedChoiceMetadata(SimpleMetadata):
    def get_field_info(self, field):
        field_info = super(GroupedChoiceMetadata, self).get_field_info(field)

        if field_info.get('type') is 'choice':
            field_info['grouped_choices']= field.grouped_choices

        return field_info

I wonder why grouped_choices aren't returned by default?

@tomchristie Do you perhaps have some insights for me?

@tomchristie
Copy link
Member

@wernerhp - Mostly likely simply historical. Expect they didn't exist when we added the metadata API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants