Drop label from serializer fields when not needed

This commit is contained in:
Tom Christie 2014-09-15 09:50:51 +01:00
parent afb28a44ad
commit 40dc588a37
4 changed files with 70 additions and 56 deletions

View File

@ -80,6 +80,10 @@ def set_value(dictionary, keys, value):
dictionary[keys[-1]] = value dictionary[keys[-1]] = value
def field_name_to_label(field_name):
return field_name.replace('_', ' ').capitalize()
class SkipField(Exception): class SkipField(Exception):
pass pass
@ -158,7 +162,7 @@ class Field(object):
# `self.label` should deafult to being based on the field name. # `self.label` should deafult to being based on the field name.
if self.label is None: if self.label is None:
self.label = self.field_name.replace('_', ' ').capitalize() self.label = field_name_to_label(self.field_name)
# self.source should default to being the same as the field name. # self.source should default to being the same as the field name.
if self.source is None: if self.source is None:

View File

@ -15,11 +15,12 @@ from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils import six from django.utils import six
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.utils.text import capfirst
from collections import namedtuple from collections import namedtuple
from rest_framework.compat import clean_manytomany_helptext from rest_framework.compat import clean_manytomany_helptext
from rest_framework.fields import empty, set_value, Field, SkipField from rest_framework.fields import empty, set_value, Field, SkipField
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.utils import html, modelinfo, representation from rest_framework.utils import html, model_meta, representation
import copy import copy
# Note: We do the following so that users of the framework can use this style: # Note: We do the following so that users of the framework can use this style:
@ -334,6 +335,14 @@ def lookup_class(mapping, instance):
raise KeyError('Class %s not found in lookup.', cls.__name__) raise KeyError('Class %s not found in lookup.', cls.__name__)
def needs_label(model_field, field_name):
"""
Returns `True` if the label based on the model's verbose name
is not equal to the default label it would have based on it's field name.
"""
return capfirst(model_field.verbose_name) != field_name_to_label(field_name)
class ModelSerializer(Serializer): class ModelSerializer(Serializer):
field_mapping = { field_mapping = {
models.AutoField: IntegerField, models.AutoField: IntegerField,
@ -397,54 +406,55 @@ class ModelSerializer(Serializer):
""" """
Return all the fields that should be serialized for the model. Return all the fields that should be serialized for the model.
""" """
info = modelinfo.get_field_info(self.opts.model) info = model_meta.get_field_info(self.opts.model)
ret = SortedDict() ret = SortedDict()
serializer_url_field = self.get_url_field() serializer_url_field = self.get_url_field()
if serializer_url_field: if serializer_url_field:
ret[api_settings.URL_FIELD_NAME] = serializer_url_field ret[api_settings.URL_FIELD_NAME] = serializer_url_field
serializer_pk_field = self.get_pk_field(info.pk) field_name = info.pk.name
serializer_pk_field = self.get_pk_field(field_name, info.pk)
if serializer_pk_field: if serializer_pk_field:
ret[info.pk.name] = serializer_pk_field ret[field_name] = serializer_pk_field
# Regular fields # Regular fields
for field_name, field in info.fields.items(): for field_name, field in info.fields.items():
ret[field_name] = self.get_field(field) ret[field_name] = self.get_field(field_name, field)
# Forward relations # Forward relations
for field_name, relation_info in info.forward_relations.items(): for field_name, relation_info in info.forward_relations.items():
if self.opts.depth: if self.opts.depth:
ret[field_name] = self.get_nested_field(*relation_info) ret[field_name] = self.get_nested_field(field_name, *relation_info)
else: else:
ret[field_name] = self.get_related_field(*relation_info) ret[field_name] = self.get_related_field(field_name, *relation_info)
# Reverse relations # Reverse relations
for accessor_name, relation_info in info.reverse_relations.items(): for accessor_name, relation_info in info.reverse_relations.items():
if accessor_name in self.opts.fields: if accessor_name in self.opts.fields:
if self.opts.depth: if self.opts.depth:
ret[accessor_name] = self.get_nested_field(*relation_info) ret[accessor_name] = self.get_nested_field(accessor_name, *relation_info)
else: else:
ret[accessor_name] = self.get_related_field(*relation_info) ret[accessor_name] = self.get_related_field(accessor_name, *relation_info)
return ret return ret
def get_url_field(self): def get_url_field(self):
return None return None
def get_pk_field(self, model_field): def get_pk_field(self, field_name, model_field):
""" """
Returns a default instance of the pk field. Returns a default instance of the pk field.
""" """
return self.get_field(model_field) return self.get_field(field_name, model_field)
def get_nested_field(self, model_field, related_model, to_many, has_through_model): def get_nested_field(self, field_name, model_field, related_model, to_many, has_through_model):
""" """
Creates a default instance of a nested relational field. Creates a default instance of a nested relational field.
Note that model_field will be `None` for reverse relationships. Note that model_field will be `None` for reverse relationships.
""" """
class NestedModelSerializer(ModelSerializer): # Not right! class NestedModelSerializer(ModelSerializer):
class Meta: class Meta:
model = related_model model = related_model
depth = self.opts.depth - 1 depth = self.opts.depth - 1
@ -454,7 +464,7 @@ class ModelSerializer(Serializer):
kwargs['many'] = True kwargs['many'] = True
return NestedModelSerializer(**kwargs) return NestedModelSerializer(**kwargs)
def get_related_field(self, model_field, related_model, to_many, has_through_model): def get_related_field(self, field_name, model_field, related_model, to_many, has_through_model):
""" """
Creates a default instance of a flat relational field. Creates a default instance of a flat relational field.
@ -474,8 +484,8 @@ class ModelSerializer(Serializer):
if model_field: if model_field:
if model_field.null or model_field.blank: if model_field.null or model_field.blank:
kwargs['required'] = False kwargs['required'] = False
if model_field.verbose_name: if model_field.verbose_name and needs_label(model_field, field_name):
kwargs['label'] = model_field.verbose_name kwargs['label'] = capfirst(model_field.verbose_name)
if not model_field.editable: if not model_field.editable:
kwargs['read_only'] = True kwargs['read_only'] = True
kwargs.pop('queryset', None) kwargs.pop('queryset', None)
@ -485,7 +495,7 @@ class ModelSerializer(Serializer):
return PrimaryKeyRelatedField(**kwargs) return PrimaryKeyRelatedField(**kwargs)
def get_field(self, model_field): def get_field(self, field_name, model_field):
""" """
Creates a default instance of a basic non-relational field. Creates a default instance of a basic non-relational field.
""" """
@ -496,8 +506,8 @@ class ModelSerializer(Serializer):
if model_field.null or model_field.blank: if model_field.null or model_field.blank:
kwargs['required'] = False kwargs['required'] = False
if model_field.verbose_name is not None: if model_field.verbose_name and needs_label(model_field, field_name):
kwargs['label'] = model_field.verbose_name kwargs['label'] = capfirst(model_field.verbose_name)
if model_field.help_text: if model_field.help_text:
kwargs['help_text'] = model_field.help_text kwargs['help_text'] = model_field.help_text
@ -642,11 +652,11 @@ class HyperlinkedModelSerializer(ModelSerializer):
return HyperlinkedIdentityField(**kwargs) return HyperlinkedIdentityField(**kwargs)
def get_pk_field(self, model_field): def get_pk_field(self, field_name, model_field):
if self.opts.fields and model_field.name in self.opts.fields: if self.opts.fields and model_field.name in self.opts.fields:
return self.get_field(model_field) return self.get_field(model_field)
def get_related_field(self, model_field, related_model, to_many, has_through_model): def get_related_field(self, field_name, model_field, related_model, to_many, has_through_model):
""" """
Creates a default instance of a flat relational field. Creates a default instance of a flat relational field.
""" """
@ -665,8 +675,8 @@ class HyperlinkedModelSerializer(ModelSerializer):
if model_field: if model_field:
if model_field.null or model_field.blank: if model_field.null or model_field.blank:
kwargs['required'] = False kwargs['required'] = False
if model_field.verbose_name: if model_field.verbose_name and needs_label(model_field, field_name):
kwargs['label'] = model_field.verbose_name kwargs['label'] = capfirst(model_field.verbose_name)
if not model_field.editable: if not model_field.editable:
kwargs['read_only'] = True kwargs['read_only'] = True
kwargs.pop('queryset', None) kwargs.pop('queryset', None)

View File

@ -36,25 +36,25 @@ class RegularFieldsModel(models.Model):
REGULAR_FIELDS_REPR = """ REGULAR_FIELDS_REPR = """
TestSerializer(): TestSerializer():
auto_field = IntegerField(label='auto field', read_only=True) auto_field = IntegerField(read_only=True)
big_integer_field = IntegerField(label='big integer field') big_integer_field = IntegerField()
boolean_field = BooleanField(default=False, label='boolean field') boolean_field = BooleanField(default=False)
char_field = CharField(label='char field', max_length=100) char_field = CharField(max_length=100)
comma_seperated_integer_field = CharField(label='comma seperated integer field', max_length=100, validators=[<django.core.validators.RegexValidator object>]) comma_seperated_integer_field = CharField(max_length=100, validators=[<django.core.validators.RegexValidator object>])
date_field = DateField(label='date field') date_field = DateField()
datetime_field = DateTimeField(label='datetime field') datetime_field = DateTimeField()
decimal_field = DecimalField(decimal_places=1, label='decimal field', max_digits=3) decimal_field = DecimalField(decimal_places=1, max_digits=3)
email_field = EmailField(label='email field', max_length=100) email_field = EmailField(max_length=100)
float_field = FloatField(label='float field') float_field = FloatField()
integer_field = IntegerField(label='integer field') integer_field = IntegerField()
null_boolean_field = BooleanField(label='null boolean field', required=False) null_boolean_field = BooleanField(required=False)
positive_integer_field = IntegerField(label='positive integer field') positive_integer_field = IntegerField()
positive_small_integer_field = IntegerField(label='positive small integer field') positive_small_integer_field = IntegerField()
slug_field = SlugField(label='slug field', max_length=100) slug_field = SlugField(max_length=100)
small_integer_field = IntegerField(label='small integer field') small_integer_field = IntegerField()
text_field = CharField(label='text field') text_field = CharField()
time_field = TimeField(label='time field') time_field = TimeField()
url_field = URLField(label='url field', max_length=100) url_field = URLField(max_length=100)
""".strip() """.strip()
@ -81,9 +81,9 @@ class RelationalModel(models.Model):
RELATIONAL_FLAT_REPR = """ RELATIONAL_FLAT_REPR = """
TestSerializer(): TestSerializer():
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
foreign_key = PrimaryKeyRelatedField(label='foreign key', queryset=ForeignKeyTargetModel.objects.all()) foreign_key = PrimaryKeyRelatedField(queryset=ForeignKeyTargetModel.objects.all())
one_to_one = PrimaryKeyRelatedField(label='one to one', queryset=OneToOneTargetModel.objects.all()) one_to_one = PrimaryKeyRelatedField(queryset=OneToOneTargetModel.objects.all())
many_to_many = PrimaryKeyRelatedField(label='many to many', many=True, queryset=ManyToManyTargetModel.objects.all()) many_to_many = PrimaryKeyRelatedField(many=True, queryset=ManyToManyTargetModel.objects.all())
""".strip() """.strip()
@ -92,22 +92,22 @@ TestSerializer():
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
foreign_key = NestedModelSerializer(read_only=True): foreign_key = NestedModelSerializer(read_only=True):
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100) name = CharField(max_length=100)
one_to_one = NestedModelSerializer(read_only=True): one_to_one = NestedModelSerializer(read_only=True):
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100) name = CharField(max_length=100)
many_to_many = NestedModelSerializer(many=True, read_only=True): many_to_many = NestedModelSerializer(many=True, read_only=True):
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100) name = CharField(max_length=100)
""".strip() """.strip()
HYPERLINKED_FLAT_REPR = """ HYPERLINKED_FLAT_REPR = """
TestSerializer(): TestSerializer():
url = HyperlinkedIdentityField(view_name='relationalmodel-detail') url = HyperlinkedIdentityField(view_name='relationalmodel-detail')
foreign_key = HyperlinkedRelatedField(label='foreign key', queryset=ForeignKeyTargetModel.objects.all(), view_name='foreignkeytargetmodel-detail') foreign_key = HyperlinkedRelatedField(queryset=ForeignKeyTargetModel.objects.all(), view_name='foreignkeytargetmodel-detail')
one_to_one = HyperlinkedRelatedField(label='one to one', queryset=OneToOneTargetModel.objects.all(), view_name='onetoonetargetmodel-detail') one_to_one = HyperlinkedRelatedField(queryset=OneToOneTargetModel.objects.all(), view_name='onetoonetargetmodel-detail')
many_to_many = HyperlinkedRelatedField(label='many to many', many=True, queryset=ManyToManyTargetModel.objects.all(), view_name='manytomanytargetmodel-detail') many_to_many = HyperlinkedRelatedField(many=True, queryset=ManyToManyTargetModel.objects.all(), view_name='manytomanytargetmodel-detail')
""".strip() """.strip()
@ -116,13 +116,13 @@ TestSerializer():
url = HyperlinkedIdentityField(view_name='relationalmodel-detail') url = HyperlinkedIdentityField(view_name='relationalmodel-detail')
foreign_key = NestedModelSerializer(read_only=True): foreign_key = NestedModelSerializer(read_only=True):
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100) name = CharField(max_length=100)
one_to_one = NestedModelSerializer(read_only=True): one_to_one = NestedModelSerializer(read_only=True):
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100) name = CharField(max_length=100)
many_to_many = NestedModelSerializer(many=True, read_only=True): many_to_many = NestedModelSerializer(many=True, read_only=True):
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100) name = CharField(max_length=100)
""".strip() """.strip()
@ -180,4 +180,4 @@ class TestSerializerMappings(TestCase):
# class Meta: # class Meta:
# model = ManyToManyTargetModel # model = ManyToManyTargetModel
# fields = ('id', 'name', 'reverse_many_to_many') # fields = ('id', 'name', 'reverse_many_to_many')
# print repr(TestSerializer()) # print repr(TestSerializer())