django-rest-framework/rest_framework/serializers.py

660 lines
23 KiB
Python
Raw Normal View History

2013-04-25 15:47:34 +04:00
"""
Serializers and ModelSerializers are similar to Forms and ModelForms.
Unlike forms, they are not constrained to dealing with HTML output, and
form encoded input.
Serialization in REST framework is a two-phase process:
1. Serializers marshal between complex types like model instances, and
python primitives.
2. The process of marshalling between python primitives and request and
2013-04-25 15:47:34 +04:00
response content is handled by parsers and renderers.
"""
2014-09-09 20:46:28 +04:00
from django.core import validators
2014-09-08 17:24:05 +04:00
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import six
2014-08-29 19:46:26 +04:00
from collections import namedtuple, OrderedDict
2014-09-10 16:52:16 +04:00
from rest_framework.compat import clean_manytomany_helptext
2014-09-05 19:29:46 +04:00
from rest_framework.fields import empty, set_value, Field, SkipField
from rest_framework.settings import api_settings
2014-09-09 20:46:28 +04:00
from rest_framework.utils import html, modelinfo, representation
2014-08-29 19:46:26 +04:00
import copy
2012-11-05 14:56:30 +04:00
# Note: We do the following so that users of the framework can use this style:
#
# example_field = serializers.CharField(...)
#
2013-05-28 18:09:23 +04:00
# This helps keep the separation between model fields, form fields, and
2012-11-05 14:56:30 +04:00
# serializer fields more explicit.
2014-05-17 02:05:33 +04:00
from rest_framework.relations import * # NOQA
from rest_framework.fields import * # NOQA
2014-08-29 19:46:26 +04:00
FieldResult = namedtuple('FieldResult', ['field', 'value', 'error'])
2014-08-29 19:46:26 +04:00
class BaseSerializer(Field):
2014-09-05 19:29:46 +04:00
"""
The BaseSerializer class provides a minimal class which may be used
for writing custom serializer implementations.
"""
2014-08-29 19:46:26 +04:00
def __init__(self, instance=None, data=None, **kwargs):
super(BaseSerializer, self).__init__(**kwargs)
self.instance = instance
self._initial_data = data
2014-08-29 19:46:26 +04:00
def to_native(self, data):
2014-09-05 19:29:46 +04:00
raise NotImplementedError('`to_native()` must be implemented.')
2014-08-29 19:46:26 +04:00
def to_primative(self, instance):
2014-09-05 19:29:46 +04:00
raise NotImplementedError('`to_primative()` must be implemented.')
2014-09-05 19:29:46 +04:00
def update(self, instance, attrs):
raise NotImplementedError('`update()` must be implemented.')
2014-09-05 19:29:46 +04:00
def create(self, attrs):
raise NotImplementedError('`create()` must be implemented.')
2014-08-29 19:46:26 +04:00
def save(self, extras=None):
2014-09-08 17:24:05 +04:00
attrs = self.validated_data
2014-08-29 19:46:26 +04:00
if extras is not None:
2014-09-08 17:24:05 +04:00
attrs = dict(list(attrs.items()) + list(extras.items()))
2014-08-29 19:46:26 +04:00
if self.instance is not None:
2014-09-08 17:24:05 +04:00
self.update(self.instance, attrs)
else:
2014-09-08 17:24:05 +04:00
self.instance = self.create(attrs)
2014-08-29 19:46:26 +04:00
return self.instance
2014-09-05 19:29:46 +04:00
def is_valid(self, raise_exception=False):
if not hasattr(self, '_validated_data'):
try:
self._validated_data = self.to_native(self._initial_data)
except ValidationError as exc:
self._validated_data = {}
2014-09-08 17:24:05 +04:00
self._errors = exc.message_dict
2014-09-05 19:29:46 +04:00
else:
self._errors = {}
if self._errors and raise_exception:
raise ValidationError(self._errors)
return not bool(self._errors)
2014-08-29 19:46:26 +04:00
@property
def data(self):
if not hasattr(self, '_data'):
if self.instance is not None:
self._data = self.to_primative(self.instance)
elif self._initial_data is not None:
self._data = {
field_name: field.get_value(self._initial_data)
for field_name, field in self.fields.items()
}
else:
self._data = self.get_initial()
return self._data
2014-08-29 19:46:26 +04:00
@property
def errors(self):
if not hasattr(self, '_errors'):
msg = 'You must call `.is_valid()` before accessing `.errors`.'
raise AssertionError(msg)
return self._errors
2014-08-29 19:46:26 +04:00
@property
def validated_data(self):
if not hasattr(self, '_validated_data'):
msg = 'You must call `.is_valid()` before accessing `.validated_data`.'
raise AssertionError(msg)
return self._validated_data
2014-08-29 19:46:26 +04:00
class SerializerMetaclass(type):
"""
2014-08-29 19:46:26 +04:00
This metaclass sets a dictionary named `base_fields` on the class.
2014-09-10 16:52:16 +04:00
Any instances of `Field` included as attributes on either the class
or on any of its superclasses will be include in the
`base_fields` dictionary.
"""
2014-08-29 19:46:26 +04:00
@classmethod
def _get_fields(cls, bases, attrs):
fields = [(field_name, attrs.pop(field_name))
for field_name, obj in list(attrs.items())
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1]._creation_counter)
2014-08-29 19:46:26 +04:00
# If this class is subclassing another Serializer, add that Serializer's
# fields. Note that we loop over the bases in *reverse*. This is necessary
# in order to maintain the correct order of fields.
for base in bases[::-1]:
if hasattr(base, 'base_fields'):
fields = list(base.base_fields.items()) + fields
2014-08-29 19:46:26 +04:00
return OrderedDict(fields)
def __new__(cls, name, bases, attrs):
2014-08-29 19:46:26 +04:00
attrs['base_fields'] = cls._get_fields(bases, attrs)
return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
2014-08-29 19:46:26 +04:00
@six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer):
2014-08-29 19:46:26 +04:00
def __new__(cls, *args, **kwargs):
2014-09-09 20:46:28 +04:00
if kwargs.pop('many', False):
kwargs['child'] = cls()
return ListSerializer(*args, **kwargs)
return super(Serializer, cls).__new__(cls, *args, **kwargs)
2014-08-29 19:46:26 +04:00
def __init__(self, *args, **kwargs):
2014-09-02 20:41:23 +04:00
self.context = kwargs.pop('context', {})
2014-08-29 19:46:26 +04:00
kwargs.pop('partial', None)
kwargs.pop('many', False)
2014-08-29 19:46:26 +04:00
super(Serializer, self).__init__(*args, **kwargs)
2014-08-29 19:46:26 +04:00
# Every new serializer is created with a clone of the field instances.
# This allows users to dynamically modify the fields on a serializer
# instance without affecting every other serializer class.
self.fields = self.get_fields()
2014-08-29 19:46:26 +04:00
# Setup all the child fields, to provide them with the current context.
for field_name, field in self.fields.items():
2014-08-29 19:46:26 +04:00
field.bind(field_name, self, self)
2014-08-29 19:46:26 +04:00
def get_fields(self):
return copy.deepcopy(self.base_fields)
2014-08-29 19:46:26 +04:00
def bind(self, field_name, parent, root):
# If the serializer is used as a field then when it becomes bound
# it also needs to bind all its child fields.
super(Serializer, self).bind(field_name, parent, root)
for field_name, field in self.fields.items():
2014-08-29 19:46:26 +04:00
field.bind(field_name, self, root)
2014-08-29 19:46:26 +04:00
def get_initial(self):
return {
field.field_name: field.get_initial()
for field in self.fields.values()
}
2014-08-29 19:46:26 +04:00
def get_value(self, dictionary):
# We override the default field access in order to support
# nested HTML forms.
if html.is_html_input(dictionary):
return html.parse_html_dict(dictionary, prefix=self.field_name)
return dictionary.get(self.field_name, empty)
2012-10-24 14:39:17 +04:00
2014-08-29 19:46:26 +04:00
def to_native(self, data):
"""
2014-08-29 19:46:26 +04:00
Dict of native values <- Dict of primitive datatypes.
"""
2014-09-05 19:29:46 +04:00
if not isinstance(data, dict):
raise ValidationError({'non_field_errors': ['Invalid data']})
2014-08-29 19:46:26 +04:00
ret = {}
errors = {}
fields = [field for field in self.fields.values() if not field.read_only]
2014-08-29 19:46:26 +04:00
for field in fields:
2014-09-05 19:29:46 +04:00
validate_method = getattr(self, 'validate_' + field.field_name, None)
2014-08-29 19:46:26 +04:00
primitive_value = field.get_value(data)
try:
2014-09-10 11:53:33 +04:00
validated_value = field.validate_value(primitive_value)
2014-09-05 19:29:46 +04:00
if validate_method is not None:
validated_value = validate_method(validated_value)
2014-08-29 19:46:26 +04:00
except ValidationError as exc:
2014-09-08 17:24:05 +04:00
errors[field.field_name] = exc.messages
2014-08-29 19:46:26 +04:00
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
2014-08-29 19:46:26 +04:00
if errors:
raise ValidationError(errors)
2014-09-08 17:24:05 +04:00
try:
return self.validate(ret)
except ValidationError, exc:
raise ValidationError({'non_field_errors': exc.messages})
2014-08-29 19:46:26 +04:00
def to_primative(self, instance):
"""
2014-08-29 19:46:26 +04:00
Object instance -> Dict of primitive datatypes.
"""
2014-08-29 19:46:26 +04:00
ret = OrderedDict()
fields = [field for field in self.fields.values() if not field.write_only]
2014-08-29 19:46:26 +04:00
for field in fields:
native_value = field.get_attribute(instance)
ret[field.field_name] = field.to_primative(native_value)
2014-08-29 19:46:26 +04:00
return ret
2014-09-02 20:41:23 +04:00
def validate(self, attrs):
return attrs
2014-08-29 19:46:26 +04:00
def __iter__(self):
errors = self.errors if hasattr(self, '_errors') else {}
for field in self.fields.values():
value = self.data.get(field.field_name) if self.data else None
error = errors.get(field.field_name)
yield FieldResult(field, value, error)
2014-09-09 20:46:28 +04:00
def __repr__(self):
return representation.serializer_repr(self, indent=1)
2014-08-29 19:46:26 +04:00
class ListSerializer(BaseSerializer):
child = None
initial = []
2014-08-29 19:46:26 +04:00
def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
assert self.child is not None, '`child` is a required argument.'
2014-09-02 20:41:23 +04:00
self.context = kwargs.pop('context', {})
2014-08-29 19:46:26 +04:00
kwargs.pop('partial', None)
2014-08-29 19:46:26 +04:00
super(ListSerializer, self).__init__(*args, **kwargs)
self.child.bind('', self, self)
2014-08-29 19:46:26 +04:00
def bind(self, field_name, parent, root):
# If the list is used as a field then it needs to provide
# the current context to the child serializer.
super(ListSerializer, self).bind(field_name, parent, root)
self.child.bind(field_name, self, root)
2014-08-29 19:46:26 +04:00
def get_value(self, dictionary):
# We override the default field access in order to support
# lists in HTML forms.
if is_html_input(dictionary):
return html.parse_html_list(dictionary, prefix=self.field_name)
return dictionary.get(self.field_name, empty)
2013-03-19 18:26:48 +04:00
2014-08-29 19:46:26 +04:00
def to_native(self, data):
"""
2014-08-29 19:46:26 +04:00
List of dicts of native values <- List of dicts of primitive datatypes.
"""
2014-08-29 19:46:26 +04:00
if html.is_html_input(data):
data = html.parse_html_list(data)
2014-08-29 19:46:26 +04:00
return [self.child.validate(item) for item in data]
2014-08-29 19:46:26 +04:00
def to_primative(self, data):
2013-01-31 00:38:11 +04:00
"""
2014-08-29 19:46:26 +04:00
List of object instances -> List of dicts of primitive datatypes.
2013-01-31 00:38:11 +04:00
"""
2014-08-29 19:46:26 +04:00
return [self.child.to_primative(item) for item in data]
2014-08-29 19:46:26 +04:00
def create(self, attrs_list):
return [self.child.create(attrs) for attrs in attrs_list]
2014-08-29 19:46:26 +04:00
def save(self):
if self.instance is not None:
self.update(self.instance, self.validated_data)
self.instance = self.create(self.validated_data)
return self.instance
2014-09-09 20:46:28 +04:00
def __repr__(self):
return representation.list_repr(self, indent=1)
2014-08-29 19:46:26 +04:00
class ModelSerializerOptions(object):
"""
Meta class options for ModelSerializer
"""
def __init__(self, meta):
2014-08-29 19:46:26 +04:00
self.model = getattr(meta, 'model')
self.fields = getattr(meta, 'fields', ())
self.depth = getattr(meta, 'depth', 0)
2012-10-04 16:28:14 +04:00
class ModelSerializer(Serializer):
field_mapping = {
models.AutoField: IntegerField,
2014-09-09 20:46:28 +04:00
models.BigIntegerField: IntegerField,
models.BooleanField: BooleanField,
models.CharField: CharField,
models.CommaSeparatedIntegerField: CharField,
models.DateField: DateField,
models.DateTimeField: DateTimeField,
models.DecimalField: DecimalField,
models.EmailField: EmailField,
models.FileField: FileField,
models.FloatField: FloatField,
2014-09-10 11:53:33 +04:00
models.ImageField: ImageField,
models.IntegerField: IntegerField,
2014-09-09 20:46:28 +04:00
models.NullBooleanField: BooleanField,
models.PositiveIntegerField: IntegerField,
models.PositiveSmallIntegerField: IntegerField,
2014-09-09 20:46:28 +04:00
models.SlugField: SlugField,
models.SmallIntegerField: IntegerField,
models.TextField: CharField,
2014-09-02 20:41:23 +04:00
models.TimeField: TimeField,
models.URLField: URLField,
}
2014-08-29 19:46:26 +04:00
_options_class = ModelSerializerOptions
def __init__(self, *args, **kwargs):
self.opts = self._options_class(self.Meta)
super(ModelSerializer, self).__init__(*args, **kwargs)
2014-09-05 19:29:46 +04:00
def create(self, attrs):
2014-09-02 20:41:23 +04:00
ModelClass = self.opts.model
2014-09-05 19:29:46 +04:00
return ModelClass.objects.create(**attrs)
2014-09-02 20:41:23 +04:00
2014-09-05 19:29:46 +04:00
def update(self, obj, attrs):
for attr, value in attrs.items():
2014-09-02 20:41:23 +04:00
setattr(obj, attr, value)
obj.save()
2014-08-29 19:46:26 +04:00
def get_fields(self):
# Get the explicitly declared fields.
fields = copy.deepcopy(self.base_fields)
# Add in the default fields.
for key, val in self.get_default_fields().items():
if key not in fields:
fields[key] = val
# If `fields` is set on the `Meta` class,
# then use only those fields, and in that order.
if self.opts.fields:
fields = OrderedDict([
(key, fields[key]) for key in self.opts.fields
])
return fields
def get_default_fields(self):
"""
Return all the fields that should be serialized for the model.
"""
2014-09-09 20:46:28 +04:00
info = modelinfo.get_field_info(self.opts.model)
2014-08-29 19:46:26 +04:00
ret = OrderedDict()
2014-09-10 16:52:16 +04:00
serializer_url_field = self.get_url_field()
if serializer_url_field:
ret[api_settings.URL_FIELD_NAME] = serializer_url_field
2014-09-09 20:46:28 +04:00
serializer_pk_field = self.get_pk_field(info.pk)
2014-08-19 16:28:07 +04:00
if serializer_pk_field:
2014-09-09 20:46:28 +04:00
ret[info.pk.name] = serializer_pk_field
2014-09-09 20:46:28 +04:00
# Regular fields
for field_name, field in info.fields.items():
ret[field_name] = self.get_field(field)
2014-09-09 20:46:28 +04:00
# Forward relations
for field_name, relation_info in info.forward_relations.items():
if self.opts.depth:
ret[field_name] = self.get_nested_field(*relation_info)
else:
2014-09-09 20:46:28 +04:00
ret[field_name] = self.get_related_field(*relation_info)
2014-09-09 20:46:28 +04:00
# Reverse relations
for accessor_name, relation_info in info.reverse_relations.items():
if accessor_name in self.opts.fields:
if self.opts.depth:
ret[field_name] = self.get_nested_field(*relation_info)
else:
ret[field_name] = self.get_related_field(*relation_info)
return ret
2014-09-10 16:52:16 +04:00
def get_url_field(self):
return None
def get_pk_field(self, model_field):
"""
Returns a default instance of the pk field.
"""
return self.get_field(model_field)
2014-09-09 20:46:28 +04:00
def get_nested_field(self, model_field, related_model, to_many, has_through_model):
"""
Creates a default instance of a nested relational field.
2013-04-30 11:24:33 +04:00
Note that model_field will be `None` for reverse relationships.
"""
2012-11-13 15:47:32 +04:00
class NestedModelSerializer(ModelSerializer):
class Meta:
model = related_model
depth = self.opts.depth - 1
2014-09-09 20:46:28 +04:00
kwargs = {'read_only': True}
if to_many:
kwargs['many'] = True
return NestedModelSerializer(**kwargs)
2014-09-09 20:46:28 +04:00
def get_related_field(self, model_field, related_model, to_many, has_through_model):
"""
Creates a default instance of a flat relational field.
2013-04-30 11:24:33 +04:00
Note that model_field will be `None` for reverse relationships.
"""
2014-09-09 20:46:28 +04:00
kwargs = {
'queryset': related_model._default_manager,
}
2014-09-09 20:46:28 +04:00
if to_many:
kwargs['many'] = True
if has_through_model:
kwargs['read_only'] = True
kwargs.pop('queryset', None)
2012-12-08 01:32:39 +04:00
if model_field:
2014-09-09 20:46:28 +04:00
if model_field.null or model_field.blank:
kwargs['required'] = False
2014-09-10 16:52:16 +04:00
if model_field.verbose_name:
kwargs['label'] = model_field.verbose_name
if not model_field.editable:
kwargs['read_only'] = True
2014-09-09 20:46:28 +04:00
kwargs.pop('queryset', None)
2014-09-10 16:52:16 +04:00
help_text = clean_manytomany_helptext(model_field.help_text)
if help_text:
kwargs['help_text'] = help_text
2014-09-09 20:46:28 +04:00
return PrimaryKeyRelatedField(**kwargs)
def get_field(self, model_field):
"""
Creates a default instance of a basic non-relational field.
"""
kwargs = {}
2014-09-09 20:46:28 +04:00
validator_kwarg = model_field.validators
if model_field.null or model_field.blank:
kwargs['required'] = False
2014-09-09 20:46:28 +04:00
if model_field.verbose_name is not None:
kwargs['label'] = model_field.verbose_name
2014-09-10 16:52:16 +04:00
if model_field.help_text:
kwargs['help_text'] = model_field.help_text
if isinstance(model_field, models.AutoField) or not model_field.editable:
kwargs['read_only'] = True
2014-09-09 20:46:28 +04:00
# Read only implies that the field is not required.
# We have a cleaner repr on the instance if we don't set it.
kwargs.pop('required', None)
if model_field.has_default():
kwargs['default'] = model_field.get_default()
2014-09-09 20:46:28 +04:00
# Having a default implies that the field is not required.
# We have a cleaner repr on the instance if we don't set it.
kwargs.pop('required', None)
2014-09-10 16:52:16 +04:00
if model_field.flatchoices:
# If this model field contains choices, then use a ChoiceField,
# rather than the standard serializer field for this type.
# Note that we return this prior to setting any validation type
# keyword arguments, as those are not valid initializers.
kwargs['choices'] = model_field.flatchoices
return ChoiceField(**kwargs)
2014-09-09 20:46:28 +04:00
# Ensure that max_length is passed explicitly as a keyword arg,
# rather than as a validator.
max_length = getattr(model_field, 'max_length', None)
if max_length is not None:
kwargs['max_length'] = max_length
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MaxLengthValidator)
]
# Ensure that min_length is passed explicitly as a keyword arg,
# rather than as a validator.
min_length = getattr(model_field, 'min_length', None)
if min_length is not None:
kwargs['min_length'] = min_length
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MinLengthValidator)
]
# Ensure that max_value is passed explicitly as a keyword arg,
# rather than as a validator.
max_value = next((
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MaxValueValidator)
), None)
if max_value is not None:
kwargs['max_value'] = max_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MaxValueValidator)
]
# Ensure that max_value is passed explicitly as a keyword arg,
# rather than as a validator.
min_value = next((
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MinValueValidator)
), None)
if min_value is not None:
kwargs['min_value'] = min_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MinValueValidator)
]
# URLField does not need to include the URLValidator argument,
# as it is explicitly added in.
if isinstance(model_field, models.URLField):
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.URLValidator)
]
# EmailField does not need to include the validate_email argument,
# as it is explicitly added in.
if isinstance(model_field, models.EmailField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_email
]
# SlugField do not need to include the 'validate_slug' argument,
if isinstance(model_field, models.SlugField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_slug
]
max_digits = getattr(model_field, 'max_digits', None)
if max_digits is not None:
kwargs['max_digits'] = max_digits
decimal_places = getattr(model_field, 'decimal_places', None)
if decimal_places is not None:
kwargs['decimal_places'] = decimal_places
if validator_kwarg:
kwargs['validators'] = validator_kwarg
try:
2014-01-12 18:30:26 +04:00
return self.field_mapping[model_field.__class__](**kwargs)
except KeyError:
2014-09-08 17:24:05 +04:00
return ModelField(model_field=model_field, **kwargs)
2013-01-18 23:47:57 +04:00
class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
"""
Options for HyperlinkedModelSerializer
"""
def __init__(self, meta):
super(HyperlinkedModelSerializerOptions, self).__init__(meta)
self.view_name = getattr(meta, 'view_name', None)
self.lookup_field = getattr(meta, 'lookup_field', None)
class HyperlinkedModelSerializer(ModelSerializer):
_options_class = HyperlinkedModelSerializerOptions
2014-09-09 20:46:28 +04:00
def get_url_field(self):
2014-09-10 16:52:16 +04:00
if self.opts.view_name is not None:
view_name = self.opts.view_name
else:
view_name = self.get_default_view_name(self.opts.model)
2014-09-09 20:46:28 +04:00
kwargs = {
2014-09-10 16:52:16 +04:00
'view_name': view_name
2014-09-09 20:46:28 +04:00
}
if self.opts.lookup_field:
kwargs['lookup_field'] = self.opts.lookup_field
2014-09-10 16:52:16 +04:00
2014-09-09 20:46:28 +04:00
return HyperlinkedIdentityField(**kwargs)
2014-09-10 16:52:16 +04:00
def get_pk_field(self, model_field):
if self.opts.fields and model_field.name in self.opts.fields:
return self.get_field(model_field)
2014-09-09 20:46:28 +04:00
def get_related_field(self, model_field, related_model, to_many, has_through_model):
"""
Creates a default instance of a flat relational field.
"""
2014-09-09 20:46:28 +04:00
kwargs = {
'queryset': related_model._default_manager,
'view_name': self.get_default_view_name(related_model),
}
if to_many:
kwargs['many'] = True
if has_through_model:
kwargs['read_only'] = True
kwargs.pop('queryset', None)
if model_field:
2014-09-09 20:46:28 +04:00
if model_field.null or model_field.blank:
kwargs['required'] = False
2014-09-10 16:52:16 +04:00
if model_field.verbose_name:
kwargs['label'] = model_field.verbose_name
2014-09-09 20:46:28 +04:00
if not model_field.editable:
kwargs['read_only'] = True
kwargs.pop('queryset', None)
2014-09-10 16:52:16 +04:00
help_text = clean_manytomany_helptext(model_field.help_text)
if help_text:
kwargs['help_text'] = help_text
2014-09-09 20:46:28 +04:00
return HyperlinkedRelatedField(**kwargs)
2014-09-09 20:46:28 +04:00
def get_default_view_name(self, model):
"""
2014-09-09 20:46:28 +04:00
Return the view name to use for related models.
"""
2014-09-09 20:46:28 +04:00
return '%(model_name)s-detail' % {
'app_label': model._meta.app_label,
'model_name': model._meta.object_name.lower()
}