diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6ef539754..3f098c42a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -504,6 +504,7 @@ class HyperlinkedRelatedField(RelatedField): except: raise ValueError("Hyperlinked field requires 'view_name' kwarg") + self.view_namespace = kwargs.pop('view_namespace',None) self.slug_field = kwargs.pop('slug_field', self.slug_field) default_slug_kwarg = self.slug_url_kwarg or self.slug_field self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) @@ -519,7 +520,13 @@ class HyperlinkedRelatedField(RelatedField): return self.slug_field def to_native(self, obj): - view_name = self.view_name + if self.view_namespace: + view_name = "%(namespace)s:%(view)s" % { + 'view':self.view_name, + 'namespace':self.view_namespace + } + else: + view_name = self.view_name request = self.context.get('request', None) format = self.format or self.context.get('format', None) pk = getattr(obj, 'pk', None) @@ -538,13 +545,13 @@ class HyperlinkedRelatedField(RelatedField): kwargs = {self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request, format=format) + return reverse(view_name, kwargs=kwargs, request=request, format=format) except: pass kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request, format=format) + return reverse(view_name, kwargs=kwargs, request=request, format=format) except: pass @@ -611,6 +618,7 @@ class HyperlinkedIdentityField(Field): # TODO: Make view_name mandatory, and have the # HyperlinkedModelSerializer set it on-the-fly self.view_name = kwargs.pop('view_name', None) + self.view_namespace = kwargs.pop('view_namespace', None) self.format = kwargs.pop('format', None) self.slug_field = kwargs.pop('slug_field', self.slug_field) @@ -623,7 +631,13 @@ class HyperlinkedIdentityField(Field): def field_to_native(self, obj, field_name): request = self.context.get('request', None) format = self.format or self.context.get('format', None) + view_namespace = self.view_namespace or self.parent.opts.view_namespace view_name = self.view_name or self.parent.opts.view_name + if view_namespace: + view_name = "%(namespace)s:%(view)s" % { + 'view':view_name, + 'namespace':view_namespace + } kwargs = {self.pk_url_kwarg: obj.pk} try: return reverse(view_name, kwargs=kwargs, request=request, format=format) @@ -637,13 +651,13 @@ class HyperlinkedIdentityField(Field): kwargs = {self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request, format=format) + return reverse(view_name, kwargs=kwargs, request=request, format=format) except: pass kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request, format=format) + return reverse(view_name, kwargs=kwargs, request=request, format=format) except: pass diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 870464f0b..a08e6a53f 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -258,6 +258,22 @@ class BrowsableAPIRenderer(BaseRenderer): media_type = 'text/html' format = 'api' template = 'rest_framework/api.html' + field_mapping = { + serializers.FloatField: forms.FloatField, + serializers.IntegerField: forms.IntegerField, + serializers.DateTimeField: forms.DateTimeField, + serializers.DateField: forms.DateField, + serializers.EmailField: forms.EmailField, + serializers.CharField: forms.CharField, + serializers.ChoiceField: forms.ChoiceField, + serializers.BooleanField: forms.BooleanField, + serializers.PrimaryKeyRelatedField: forms.ChoiceField, + serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField, + serializers.SlugRelatedField: forms.ChoiceField, + serializers.ManySlugRelatedField: forms.MultipleChoiceField, + serializers.HyperlinkedRelatedField: forms.ChoiceField, + serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField + } def get_default_renderer(self, view): """ @@ -306,22 +322,6 @@ class BrowsableAPIRenderer(BaseRenderer): return True def serializer_to_form_fields(self, serializer): - field_mapping = { - serializers.FloatField: forms.FloatField, - serializers.IntegerField: forms.IntegerField, - serializers.DateTimeField: forms.DateTimeField, - serializers.DateField: forms.DateField, - serializers.EmailField: forms.EmailField, - serializers.CharField: forms.CharField, - serializers.ChoiceField: forms.ChoiceField, - serializers.BooleanField: forms.BooleanField, - serializers.PrimaryKeyRelatedField: forms.ChoiceField, - serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField, - serializers.SlugRelatedField: forms.ChoiceField, - serializers.ManySlugRelatedField: forms.MultipleChoiceField, - serializers.HyperlinkedRelatedField: forms.ChoiceField, - serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField - } fields = {} for k, v in serializer.get_fields(True).items(): @@ -347,7 +347,7 @@ class BrowsableAPIRenderer(BaseRenderer): kwargs['label'] = k try: - fields[k] = field_mapping[v.__class__](**kwargs) + fields[k] = self.field_mapping[v.__class__](**kwargs) except KeyError: if getattr(v, 'choices', None) is not None: fields[k] = forms.ChoiceField(**kwargs) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 0f943ac15..1551cde28 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -490,6 +490,7 @@ class HyperlinkedModelSerializerOptions(ModelSerializerOptions): def __init__(self, meta): super(HyperlinkedModelSerializerOptions, self).__init__(meta) self.view_name = getattr(meta, 'view_name', None) + self.view_namespace = getattr(meta, 'view_namespace', None) class HyperlinkedModelSerializer(ModelSerializer): @@ -497,6 +498,7 @@ class HyperlinkedModelSerializer(ModelSerializer): """ _options_class = HyperlinkedModelSerializerOptions _default_view_name = '%(model_name)s-detail' + _default_view_namespace = None # default: no namespace is prepend to view_name url = HyperlinkedIdentityField() @@ -504,6 +506,8 @@ class HyperlinkedModelSerializer(ModelSerializer): super(HyperlinkedModelSerializer, self).__init__(*args, **kwargs) if self.opts.view_name is None: self.opts.view_name = self._get_default_view_name(self.opts.model) + if self.opts.view_namespace is None: + self.opts.view_namespace = self._get_default_view_namespace(self.opts.model) def _get_default_view_name(self, model): """ @@ -516,6 +520,20 @@ class HyperlinkedModelSerializer(ModelSerializer): } return self._default_view_name % format_kwargs + def _get_default_view_namespace(self, model): + """ + Return the view namespace to use if 'view_namespace' is not specified in 'Meta' + """ + if self._default_view_namespace is None: + return self._default_view_namespace + + model_meta = model._meta + format_kwargs = { + 'app_label': model_meta.app_label, + 'model_name': model_meta.object_name.lower() + } + return self._default_view_namespace % format_kwargs + def get_pk_field(self, model_field): return None @@ -529,7 +547,8 @@ class HyperlinkedModelSerializer(ModelSerializer): queryset = rel._default_manager kwargs = { 'queryset': queryset, - 'view_name': self._get_default_view_name(rel) + 'view_name': self._get_default_view_name(rel), + 'view_namespace': self._get_default_view_namespace(rel) } if to_many: return ManyHyperlinkedRelatedField(**kwargs)