From acaaa0384cb822a96c7ce19a6d45a730642b09bf Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Tue, 4 Dec 2012 13:25:08 +0100 Subject: [PATCH 1/5] HyperlinkedIdentityField now requires View-Name HyperlinkedModelSerializer sets View-Name on the Fly * serializers opt class need to be set by serializers Metaclass --- rest_framework/fields.py | 8 +++++--- rest_framework/serializers.py | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 482a3d485..9aa1d5906 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -634,9 +634,11 @@ class HyperlinkedIdentityField(Field): slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden def __init__(self, *args, **kwargs): - # TODO: Make view_name mandatory, and have the - # HyperlinkedModelSerializer set it on-the-fly - self.view_name = kwargs.pop('view_name', None) + try: + self.view_name = kwargs.pop('view_name') + except: + raise ValueError("Hyperlinked Identity field requires 'view_name' kwarg") + self.format = kwargs.pop('format', None) self.slug_field = kwargs.pop('slug_field', self.slug_field) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4519ab053..80437952f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -5,6 +5,8 @@ from decimal import Decimal from django.db import models from django.forms import widgets from django.utils.datastructures import SortedDict +from django.utils.functional import empty +from django.core.exceptions import ImproperlyConfigured from rest_framework.compat import get_concrete_model # Note: We do the following so that users of the framework can use this style: @@ -67,10 +69,34 @@ def _get_declared_fields(bases, attrs): return SortedDict(fields) +def _get_options_instance(bases, attrs): + options_class = Meta = empty + if '_options_class' in attrs: + options_class = attrs['_options_class'] + else: + for base in bases: + if hasattr(base, '_options_class'): + options_class = getattr(base, '_options_class') + break + if options_class is empty: + raise ImproperlyConfigured, 'A Serializer requires an "_options_class" attribute' + + if 'Meta' in attrs: + Meta = attrs['Meta'] + else: + for base in bases: + if hasattr(base, 'Meta'): + Meta = getattr(base, 'Meta') + break + if Meta is empty: + raise ImproperlyConfigured, 'A Serializer requires an "Meta" attribute' + + return options_class(Meta) class SerializerMetaclass(type): def __new__(cls, name, bases, attrs): attrs['base_fields'] = _get_declared_fields(bases, attrs) + attrs['opts'] = _get_options_instance(bases, attrs) return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) @@ -93,7 +119,6 @@ class BaseSerializer(Field): def __init__(self, instance=None, data=None, files=None, context=None, partial=False, **kwargs): super(BaseSerializer, self).__init__(**kwargs) - self.opts = self._options_class(self.Meta) self.parent = None self.root = None self.partial = partial @@ -503,12 +528,13 @@ class HyperlinkedModelSerializer(ModelSerializer): _options_class = HyperlinkedModelSerializerOptions _default_view_name = '%(model_name)s-detail' - url = HyperlinkedIdentityField() - def __init__(self, *args, **kwargs): - 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) + + self.base_fields.insert(0, 'url', HyperlinkedIdentityField(view_name=self.opts.view_name)) + + super(HyperlinkedModelSerializer, self).__init__(*args, **kwargs) def _get_default_view_name(self, model): """ From 5a9aa344151a589436d643bfcfc563f09c9a7270 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Tue, 4 Dec 2012 13:40:07 +0100 Subject: [PATCH 2/5] compat for django 1.3x fixed empty was introduced Django > 1.3 --- rest_framework/compat.py | 7 +++++++ rest_framework/serializers.py | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 09b763681..b09487f38 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -343,6 +343,13 @@ except ImportError: kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) return datetime.datetime(**kw) + +# empty does not exist in 1.3x +if django.VERSION >= (1, 4): + from django.utils.functional import empty +else: + empty = object() + # Markdown is optional try: import markdown diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 80437952f..80eefbf08 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -5,9 +5,8 @@ from decimal import Decimal from django.db import models from django.forms import widgets from django.utils.datastructures import SortedDict -from django.utils.functional import empty from django.core.exceptions import ImproperlyConfigured -from rest_framework.compat import get_concrete_model +from rest_framework.compat import get_concrete_model, empty # Note: We do the following so that users of the framework can use this style: # From 7f4fee21c62da9b54150e9771b7b2ca3027b72de Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Tue, 4 Dec 2012 16:08:00 +0100 Subject: [PATCH 3/5] check before inserting 'url' in HyperlinkedmodelSerializer otherwise user can't specify different URL field --- rest_framework/serializers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 80eefbf08..8e245405a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -530,8 +530,9 @@ class HyperlinkedModelSerializer(ModelSerializer): def __init__(self, *args, **kwargs): if self.opts.view_name is None: self.opts.view_name = self._get_default_view_name(self.opts.model) - - self.base_fields.insert(0, 'url', HyperlinkedIdentityField(view_name=self.opts.view_name)) + + if not 'url' in self.base_fields: + self.base_fields.insert(0, 'url', HyperlinkedIdentityField(view_name=self.opts.view_name)) super(HyperlinkedModelSerializer, self).__init__(*args, **kwargs) From 2b79036af10a707b768f1115726d5aa8fa6ceb12 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Tue, 4 Dec 2012 17:11:04 +0100 Subject: [PATCH 4/5] removed useless empty use --- rest_framework/compat.py | 6 ------ rest_framework/serializers.py | 8 ++++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b09487f38..cadc2d2b6 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -344,12 +344,6 @@ except ImportError: return datetime.datetime(**kw) -# empty does not exist in 1.3x -if django.VERSION >= (1, 4): - from django.utils.functional import empty -else: - empty = object() - # Markdown is optional try: import markdown diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8e245405a..dbc13a4ca 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -6,7 +6,7 @@ from django.db import models from django.forms import widgets from django.utils.datastructures import SortedDict from django.core.exceptions import ImproperlyConfigured -from rest_framework.compat import get_concrete_model, empty +from rest_framework.compat import get_concrete_model # Note: We do the following so that users of the framework can use this style: # @@ -69,7 +69,7 @@ def _get_declared_fields(bases, attrs): return SortedDict(fields) def _get_options_instance(bases, attrs): - options_class = Meta = empty + options_class = Meta = None if '_options_class' in attrs: options_class = attrs['_options_class'] else: @@ -77,7 +77,7 @@ def _get_options_instance(bases, attrs): if hasattr(base, '_options_class'): options_class = getattr(base, '_options_class') break - if options_class is empty: + if options_class is None: raise ImproperlyConfigured, 'A Serializer requires an "_options_class" attribute' if 'Meta' in attrs: @@ -87,7 +87,7 @@ def _get_options_instance(bases, attrs): if hasattr(base, 'Meta'): Meta = getattr(base, 'Meta') break - if Meta is empty: + if Meta is None: raise ImproperlyConfigured, 'A Serializer requires an "Meta" attribute' return options_class(Meta) From 9c72d2baadafaf8fa7c4bfc6c42b9db18767a6b5 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Tue, 4 Dec 2012 17:29:06 +0100 Subject: [PATCH 5/5] polishing --- rest_framework/compat.py | 1 - rest_framework/fields.py | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index cadc2d2b6..09b763681 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -343,7 +343,6 @@ except ImportError: kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) return datetime.datetime(**kw) - # Markdown is optional try: import markdown diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 9aa1d5906..4173816b6 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -564,17 +564,17 @@ 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 - raise ValidationError('Could not resolve URL for field using view name "%s"', view_name) + raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name) def from_native(self, value): # Convert URL -> model instance pk @@ -651,7 +651,7 @@ 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_name = self.view_name or self.parent.opts.view_name + view_name = self.view_name kwargs = {self.pk_url_kwarg: obj.pk} try: return reverse(view_name, kwargs=kwargs, request=request, format=format) @@ -665,13 +665,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