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

Add a method to return the string value used in choices of a RelatedField #3254

Closed
jamesbeith opened this issue Aug 10, 2015 · 4 comments
Closed

Comments

@jamesbeith
Copy link
Contributor

The choices property of the RelatedField class returns an OrderedDict with the values set to the __str__ (__unicode__ on Python 2) representation of the models in the queryset. I propose adding a method that subclasses can override to provide the value used in the dictionary (named label_from_instance in the following example).

An example. An address model has a foreign key relationship with a country model.

class Country(models.Model):
    name = models.CharField(max_length=255)
    iso_code = models.CharField(max_length=3, unique=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name

class Address(models.Model):
    line_1 = models.CharField(max_length=50)
    country = models.ForeignKey(Country)

A model serializer is declared using the address model.

class AddressSerializer(serializers.ModelSerializer):
    class Meta:
        model = Address
        fields = ('line_1', 'country',)

In the template a HTML select control is rendered for the user (customer) to choose a country for their address. This is achieved by using the country field's choices property. (Jinja2 template syntax)

{% with field = form.country.as_form_field() %}
  <select name="{{ field.name }}">
    {% for key, value in field.choices.items() %}
      <option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ value }}</option>
    {% endfor %}
  </select>
{% endwith %}

The rendered HTML is.

<select name="country">
  <option value="3">Australia</option>
  <option value="1" selected>France</option>
  <option value="2">United Kingdom</option>
</select>

And the user (customer) sees this in the browser.

screen shot 2015-08-10 at 09 29 36

The choices values are the __str__ representation of the model. Whilst this works for most cases there can be times when a different value displayed to the user is desired.

Continuing on the example. A different user (staff) of the system may also be editing instances of the same country model but would benefit from additional information than that of other users (customers), in this case the iso_code.

With the use of a method that subclasses can implement to provide the values for the choices this could be achieved. The serializer would be written as so.

class CountryField(serializers.PrimaryKeyRelatedField):
    def label_from_instance(self, instance):
        return '{name} ({iso_code})'.format(
            name=instance.name, iso_code=instance.iso_code)


class AddressSerializer(serializers.ModelSerializer):
    country = CountryField(queryset=Country.objects.all())

    class Meta:
        model = Address
        fields = ('line_1', 'country',)

The same template syntax could be used and the rendered HTML would now be.

<select name="country">
  <option value="3">Australia (AUS)</option>
  <option value="1" selected>France (FRA)</option>
  <option value="2">United Kingdom (GBR)</option>
</select>

And the user (staff) sees this in the browser.

screen shot 2015-08-10 at 09 30 18

Related discussion group.

@tomchristie
Copy link
Member

Thanks James - appreciate the in-depth issue.

I think something like display_value might be more appropriate (since this isn't an HTML <label>), but otherwise I think I'm completely in favor of this.

A pull request here would be great. I guess that the best place to include this in the docs would be somewhere in the "further notes" for serializer relations. - http://www.django-rest-framework.org/api-guide/relations/#further-notes

@tomchristie tomchristie added this to the 3.2.2 Release milestone Aug 10, 2015
@jamesbeith
Copy link
Contributor Author

I agree the name label_from_instance isn't quite right. It was merely taken from Django's ModelChoiceField which is essentially doing this same thing.

I'll look in to a pull request for this.

@tomchristie
Copy link
Member

I'll look in to a pull request for this.

Wünderbar. 😄

tomchristie added a commit that referenced this issue Aug 10, 2015
Add a method to return the string value used in `choices` of a `RelatedField`
@tomchristie
Copy link
Member

Great work :)

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

No branches or pull requests

2 participants