From b4b860b45b90769833a598d01d7ccf8950a2753b Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 14:54:01 +0100 Subject: moved field_mapping to be BrowsableAPIRenderer attr from local serializer_to_form_fields var to BrowsableAPIRenderer class attr in order to - allow customization when subclassing --- rest_framework/renderers.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 22fd6e74..2446f5b5 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) -- cgit v1.2.3 From 08fef1ac81cdf3fb76b6cdf2bdd0896eca513c09 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 14:58:53 +0100 Subject: Allowing custom Serializer Fields to have different BrowsableApiRendered Form Fields than CharField moved field_mapping from local serializer_to_form_fields var to BrowsableAPIRenderer class attr --- rest_framework/renderers.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 2446f5b5..748c1512 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -259,21 +259,21 @@ class BrowsableAPIRenderer(BaseRenderer): 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 - } + 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): """ -- cgit v1.2.3 From e9dfebc9c6c44bebc317611b84696582f502d1ac Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 15:27:32 +0100 Subject: clean support for view namespaces in as serializer attribute view name is prepended with namespace if existend --- rest_framework/serializers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4f68ada6..522878e4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -480,6 +480,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() @@ -497,7 +498,14 @@ class HyperlinkedModelSerializer(ModelSerializer): 'app_label': model_meta.app_label, 'model_name': model_meta.object_name.lower() } - return self._default_view_name % format_kwargs + view_name = self._default_view_name % format_kwargs + if self._default_view_namespace: + return "%(namespace)s:%(view)s" % { + 'view': view_name, + 'namespace': self._default_view_namespace + } + else: + return view_name def get_pk_field(self, model_field): return None -- cgit v1.2.3 From 607cf823313d4a3bdb0da4caddf19739a5f133b2 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 15:42:09 +0100 Subject: revert merge --- rest_framework/serializers.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 522878e4..4f68ada6 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -480,7 +480,6 @@ 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() @@ -498,14 +497,7 @@ class HyperlinkedModelSerializer(ModelSerializer): 'app_label': model_meta.app_label, 'model_name': model_meta.object_name.lower() } - view_name = self._default_view_name % format_kwargs - if self._default_view_namespace: - return "%(namespace)s:%(view)s" % { - 'view': view_name, - 'namespace': self._default_view_namespace - } - else: - return view_name + return self._default_view_name % format_kwargs def get_pk_field(self, model_field): return None -- cgit v1.2.3 From 5cd64cc551c8c519bc9797ebf4336bd8c6128250 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 16:02:03 +0100 Subject: Fields specify what FormFieldClass should be used by BrowsableApiRenderer added SerializerField Attribute "form_field_class" and defaults for existing Fields --- rest_framework/fields.py | 18 ++++++++++++++++-- rest_framework/renderers.py | 24 +----------------------- 2 files changed, 17 insertions(+), 25 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index a4e29a30..b8e1e2ad 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -7,6 +7,7 @@ from django.core import validators from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.urlresolvers import resolve, get_script_prefix from django.conf import settings +from django import forms from django.forms import widgets from django.forms.models import ModelChoiceIterator from django.utils.encoding import is_protected_type, smart_unicode @@ -31,6 +32,7 @@ class Field(object): creation_counter = 0 empty = '' type_name = None + form_field_class = forms.CharField def __init__(self, source=None): self.parent = None @@ -374,6 +376,7 @@ class PrimaryKeyRelatedField(RelatedField): Represents a to-one relationship as a pk value. """ default_read_only = False + form_field_class = forms.ChoiceField # TODO: Remove these field hacks... def prepare_value(self, obj): @@ -420,6 +423,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): Represents a to-many relationship as a pk value. """ default_read_only = False + form_field_class = forms.MultipleChoiceField def prepare_value(self, obj): return self.to_native(obj.pk) @@ -463,6 +467,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): class SlugRelatedField(RelatedField): default_read_only = False + form_field_class = forms.ChoiceField def __init__(self, *args, **kwargs): self.slug_field = kwargs.pop('slug_field', None) @@ -484,7 +489,7 @@ class SlugRelatedField(RelatedField): class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField): - pass + form_field_class = forms.MultipleChoiceField ### Hyperlinked relationships @@ -497,6 +502,7 @@ class HyperlinkedRelatedField(RelatedField): slug_field = 'slug' slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden default_read_only = False + form_field_class = forms.ChoiceField def __init__(self, *args, **kwargs): try: @@ -593,7 +599,7 @@ class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField): """ Represents a to-many relationship, using hyperlinking. """ - pass + form_field_class = forms.MultipleChoiceField class HyperlinkedIdentityField(Field): @@ -651,6 +657,7 @@ class HyperlinkedIdentityField(Field): class BooleanField(WritableField): type_name = 'BooleanField' + form_field_class = forms.BooleanField widget = widgets.CheckboxInput default_error_messages = { 'invalid': _(u"'%s' value must be either True or False."), @@ -672,6 +679,7 @@ class BooleanField(WritableField): class CharField(WritableField): type_name = 'CharField' + form_field_class = forms.CharField def __init__(self, max_length=None, min_length=None, *args, **kwargs): self.max_length, self.min_length = max_length, min_length @@ -699,6 +707,7 @@ class CharField(WritableField): class ChoiceField(WritableField): type_name = 'ChoiceField' + form_field_class = forms.ChoiceField widget = widgets.Select default_error_messages = { 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'), @@ -745,6 +754,7 @@ class ChoiceField(WritableField): class EmailField(CharField): type_name = 'EmailField' + form_field_class = forms.EmailField default_error_messages = { 'invalid': _('Enter a valid e-mail address.'), @@ -767,6 +777,7 @@ class EmailField(CharField): class DateField(WritableField): type_name = 'DateField' + form_field_class = forms.DateField default_error_messages = { 'invalid': _(u"'%s' value has an invalid date format. It must be " @@ -804,6 +815,7 @@ class DateField(WritableField): class DateTimeField(WritableField): type_name = 'DateTimeField' + form_field_class = forms.DateTimeField default_error_messages = { 'invalid': _(u"'%s' value has an invalid format. It must be in " @@ -858,6 +870,7 @@ class DateTimeField(WritableField): class IntegerField(WritableField): type_name = 'IntegerField' + form_field_class = forms.IntegerField default_error_messages = { 'invalid': _('Enter a whole number.'), @@ -887,6 +900,7 @@ class IntegerField(WritableField): class FloatField(WritableField): type_name = 'FloatField' + form_field_class = forms.FloatField default_error_messages = { 'invalid': _("'%s' value must be a float."), diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 748c1512..4f3aa02c 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -258,22 +258,6 @@ 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): """ @@ -346,13 +330,7 @@ class BrowsableAPIRenderer(BaseRenderer): kwargs['label'] = k - try: - fields[k] = self.field_mapping[v.__class__](**kwargs) - except KeyError: - if getattr(v, 'choices', None) is not None: - fields[k] = forms.ChoiceField(**kwargs) - else: - fields[k] = forms.CharField(**kwargs) + fields[k] = v.form_field_class(**kwargs) return fields def get_form(self, view, method, request): -- cgit v1.2.3 From 8b0561c57e4684ac440d36b39069a6c7f6168a02 Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Tue, 20 Nov 2012 23:09:47 -0800 Subject: Cache all fields on serializer init, not just default fields. --- rest_framework/serializers.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index f7918c4c..efd564d4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -103,7 +103,7 @@ class BaseSerializer(Field): self.init_data = data self.init_files = files self.object = instance - self.default_fields = self.get_default_fields() + self.serialize_fields = self.get_fields() self._data = None self._files = None @@ -134,7 +134,7 @@ class BaseSerializer(Field): field.initialize(parent=self, field_name=key) # Add in the default fields - for key, val in self.default_fields.items(): + for key, val in self.get_default_fields().items(): if key not in ret: ret[key] = val @@ -181,8 +181,7 @@ class BaseSerializer(Field): ret = self._dict_class() ret.fields = {} - fields = self.get_fields() - for field_name, field in fields.items(): + for field_name, field in self.serialize_fields.items(): key = self.get_field_key(field_name) value = field.field_to_native(obj, field_name) ret[key] = value @@ -194,9 +193,8 @@ class BaseSerializer(Field): Core of deserialization, together with `restore_object`. Converts a dictionary of data into a dictionary of deserialized fields. """ - fields = self.get_fields() reverted_data = {} - for field_name, field in fields.items(): + for field_name, field in self.serialize_fields.items(): try: field.field_from_native(data, files, field_name, reverted_data) except ValidationError as err: @@ -208,10 +206,7 @@ class BaseSerializer(Field): """ Run `validate_()` and `validate()` methods on the serializer """ - # TODO: refactor this so we're not determining the fields again - fields = self.get_fields() - - for field_name, field in fields.items(): + for field_name, field in self.serialize_fields.items(): try: validate_method = getattr(self, 'validate_%s' % field_name, None) if validate_method: -- cgit v1.2.3 From e03bb9c2fe53591c40707569d091b8793ea1000e Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Tue, 20 Nov 2012 23:17:30 -0800 Subject: Change pagination to update Serializer.serialize_fields --- rest_framework/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index d241ade7..fdffec35 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -62,7 +62,7 @@ class BasePaginationSerializer(serializers.Serializer): super(BasePaginationSerializer, self).__init__(*args, **kwargs) results_field = self.results_field object_serializer = self.opts.object_serializer_class - self.fields[results_field] = object_serializer(source='object_list') + self.serialize_fields[results_field] = object_serializer(source='object_list') def to_native(self, obj): """ -- cgit v1.2.3 From e9944f82d1efd7c6bf89ca02fb9e41b9b8973129 Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Thu, 22 Nov 2012 10:50:29 -0800 Subject: Keep Serializer.fields API consistent while caching values. --- rest_framework/pagination.py | 2 +- rest_framework/serializers.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index fdffec35..d241ade7 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -62,7 +62,7 @@ class BasePaginationSerializer(serializers.Serializer): super(BasePaginationSerializer, self).__init__(*args, **kwargs) results_field = self.results_field object_serializer = self.opts.object_serializer_class - self.serialize_fields[results_field] = object_serializer(source='object_list') + self.fields[results_field] = object_serializer(source='object_list') def to_native(self, obj): """ diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index efd564d4..380c67e4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -60,7 +60,7 @@ def _get_declared_fields(bases, attrs): # 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 the correct order of fields. + # in order to maintain the correct order of fields. for base in bases[::-1]: if hasattr(base, 'base_fields'): fields = base.base_fields.items() + fields @@ -94,7 +94,6 @@ class BaseSerializer(Field): def __init__(self, instance=None, data=None, files=None, context=None, **kwargs): super(BaseSerializer, self).__init__(**kwargs) self.opts = self._options_class(self.Meta) - self.fields = copy.deepcopy(self.base_fields) self.parent = None self.root = None @@ -103,7 +102,7 @@ class BaseSerializer(Field): self.init_data = data self.init_files = files self.object = instance - self.serialize_fields = self.get_fields() + self.fields = self.get_fields() self._data = None self._files = None @@ -128,7 +127,8 @@ class BaseSerializer(Field): ret = SortedDict() # Get the explicitly declared fields - for key, field in self.fields.items(): + base_fields = copy.deepcopy(self.base_fields) + for key, field in base_fields.items(): ret[key] = field # Set up the field field.initialize(parent=self, field_name=key) @@ -181,7 +181,7 @@ class BaseSerializer(Field): ret = self._dict_class() ret.fields = {} - for field_name, field in self.serialize_fields.items(): + for field_name, field in self.fields.items(): key = self.get_field_key(field_name) value = field.field_to_native(obj, field_name) ret[key] = value @@ -194,7 +194,7 @@ class BaseSerializer(Field): Converts a dictionary of data into a dictionary of deserialized fields. """ reverted_data = {} - for field_name, field in self.serialize_fields.items(): + for field_name, field in self.fields.items(): try: field.field_from_native(data, files, field_name, reverted_data) except ValidationError as err: @@ -206,7 +206,7 @@ class BaseSerializer(Field): """ Run `validate_()` and `validate()` methods on the serializer """ - for field_name, field in self.serialize_fields.items(): + for field_name, field in self.fields.items(): try: validate_method = getattr(self, 'validate_%s' % field_name, None) if validate_method: -- cgit v1.2.3 From 08e7818530757fe4b5fb75ba9c6b1db2bf54ee5d Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Thu, 22 Nov 2012 11:27:55 -0800 Subject: More consistent iteration over default_fields, per feedback. --- rest_framework/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 380c67e4..24fbcb79 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -134,7 +134,8 @@ class BaseSerializer(Field): field.initialize(parent=self, field_name=key) # Add in the default fields - for key, val in self.get_default_fields().items(): + default_fields = self.get_default_fields() + for key, val in self.default_fields.items(): if key not in ret: ret[key] = val -- cgit v1.2.3 From 2e36e0c9106ad8de49ce8c169083ec1d09448458 Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Thu, 22 Nov 2012 12:22:30 -0800 Subject: Remove unneeded and incorrect self reference --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 24fbcb79..546abc06 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -135,7 +135,7 @@ class BaseSerializer(Field): # Add in the default fields default_fields = self.get_default_fields() - for key, val in self.default_fields.items(): + for key, val in default_fields.items(): if key not in ret: ret[key] = val -- cgit v1.2.3 From 412f737ab2ea10bfb41e1737e338996f9f458320 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Nov 2012 13:08:57 +0000 Subject: Typo. Fixes #437. --- rest_framework/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 655b78a3..258eb9ad 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -109,6 +109,6 @@ class DjangoModelPermissions(BasePermission): if (request.user and request.user.is_authenticated() and - request.user.has_perms(perms, obj)): + request.user.has_perm(perms, obj)): return True return False -- cgit v1.2.3 From 95aa99d8dfc4b073bf549f5acba8ef2387512c86 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Nov 2012 13:09:09 +0000 Subject: Version 2.1.5 --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index a2233f3d..c1452d52 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '2.1.4' +__version__ = '2.1.5' VERSION = __version__ # synonym -- cgit v1.2.3 From fd89bca35f065cd3917fffc79b59927460a88a3a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Nov 2012 13:21:18 +0000 Subject: Version 2.1.6. AKA: I am a doofus. --- rest_framework/__init__.py | 2 +- rest_framework/permissions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index c1452d52..48cebbc5 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '2.1.5' +__version__ = '2.1.6' VERSION = __version__ # synonym diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 258eb9ad..655b78a3 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -109,6 +109,6 @@ class DjangoModelPermissions(BasePermission): if (request.user and request.user.is_authenticated() and - request.user.has_perm(perms, obj)): + request.user.has_perms(perms, obj)): return True return False -- cgit v1.2.3 From 85a921c7efcea50a5d594082f0e4ddeefd95402f Mon Sep 17 00:00:00 2001 From: Mark Hughes Date: Sat, 24 Nov 2012 17:18:32 +0000 Subject: Added setter to user property --- rest_framework/request.py | 9 +++++++++ rest_framework/tests/request.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/request.py b/rest_framework/request.py index a1827ba4..39c64321 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -169,6 +169,15 @@ class Request(object): self._user, self._auth = self._authenticate() return self._user + @user.setter + def user(self, value): + """ + Sets the user on the current request. This is necessary to maintain + compatilbility with django.contrib.auth where the user proprety is + set in the login and logout functions. + """ + self._user = value + @property def auth(self): """ diff --git a/rest_framework/tests/request.py b/rest_framework/tests/request.py index ff48f3fa..2850992d 100644 --- a/rest_framework/tests/request.py +++ b/rest_framework/tests/request.py @@ -3,6 +3,8 @@ Tests for content parsing, and form-overloaded content parsing. """ from django.conf.urls.defaults import patterns from django.contrib.auth.models import User +from django.contrib.auth import authenticate, login, logout +from django.contrib.sessions.middleware import SessionMiddleware from django.test import TestCase, Client from django.utils import simplejson as json @@ -276,3 +278,29 @@ class TestContentParsingWithAuthentication(TestCase): # response = self.csrf_client.post('/', content) # self.assertEqual(status.OK, response.status_code, "POST data is malformed") + + +class TestUserSetter(TestCase): + + def setUp(self): + # Pass request object through session middleware so session is + # available to login and logout functions + self.request = Request(factory.get('/')) + SessionMiddleware().process_request(self.request) + + User.objects.create_user('ringo', 'starr@thebeatles.com', 'yellow') + self.user = authenticate(username='ringo', password='yellow') + + def test_user_can_be_set(self): + self.request.user = self.user + self.assertEqual(self.request.user, self.user) + + def test_user_can_login(self): + login(self.request, self.user) + self.assertEqual(self.request.user, self.user) + + def test_user_can_logout(self): + self.request.user = self.user + self.assertFalse(self.request.user.is_anonymous()) + logout(self.request) + self.assertTrue(self.request.user.is_anonymous()) -- cgit v1.2.3 From 731443b71eaa94a624952bb4ea5b142ac884cccb Mon Sep 17 00:00:00 2001 From: Fabian Büchler Date: Tue, 27 Nov 2012 10:13:15 +0100 Subject: Renderer negotiation: media_type specificty evaluation weak The `DefaultContentNegotiation` handler uses For example: Google Chrome sends an Accept-header of `Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`, when I request a *.png URL. After matching the media-types with the available renderers (in my case only a custom `PNGRenderer` with a `media_type='image/png'`), only `*/*;q=0.8` is left, which happens to have the same length as the "image/png" media-type defined by the renderer (9 characters). The specificity of the renderer's media-type over the Accept-header's one is only determined by length. Using your `_MediaType.precedence` would be preferable in my eyes. Regards, Fabian --- rest_framework/negotiation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py index dae38477..ee2800a6 100644 --- a/rest_framework/negotiation.py +++ b/rest_framework/negotiation.py @@ -2,6 +2,7 @@ from django.http import Http404 from rest_framework import exceptions from rest_framework.settings import api_settings from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches +from rest_framework.utils.mediatypes import _MediaType class BaseContentNegotiation(object): @@ -48,7 +49,8 @@ class DefaultContentNegotiation(BaseContentNegotiation): for media_type in media_type_set: if media_type_matches(renderer.media_type, media_type): # Return the most specific media type as accepted. - if len(renderer.media_type) > len(media_type): + if (_MediaType(renderer.media_type).precedence > + _MediaType(media_type).precedence): # Eg client requests '*/*' # Accepted media type is 'application/json' return renderer, renderer.media_type -- cgit v1.2.3 From 7eec582d406b9b366f9d364b53d1fc509831d9b4 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Wed, 28 Nov 2012 17:04:36 +0200 Subject: Better to return 401 when failing to authenticate --- rest_framework/authtoken/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py index 3ac674e2..cfaacbe9 100644 --- a/rest_framework/authtoken/views.py +++ b/rest_framework/authtoken/views.py @@ -18,7 +18,7 @@ class ObtainAuthToken(APIView): if serializer.is_valid(): token, created = Token.objects.get_or_create(user=serializer.object['user']) return Response({'token': token.key}) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED) obtain_auth_token = ObtainAuthToken.as_view() -- cgit v1.2.3 From 19f67bd578ecd15ab057c6aff8a9cc788a459d10 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Wed, 28 Nov 2012 23:05:33 +0200 Subject: also update test with response code 401 --- rest_framework/tests/authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index 96ca9f52..d12e6e2a 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -167,7 +167,7 @@ class TokenAuthTests(TestCase): client = Client(enforce_csrf_checks=True) response = client.post('/auth-token/login/', json.dumps({'username': self.username, 'password': "badpass"}), 'application/json') - self.assertEqual(response.status_code, 400) + self.assertEqual(response.status_code, 401) def test_token_login_json_missing_fields(self): """Ensure token login view using JSON POST fails if missing fields.""" -- cgit v1.2.3 From 1b9d0eefba07cb3567ad7dbeb72c4c3b3c1f0de3 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Thu, 29 Nov 2012 09:35:22 +0200 Subject: fix forgotten 400 test --- rest_framework/tests/authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index d12e6e2a..802bc6c1 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -174,7 +174,7 @@ class TokenAuthTests(TestCase): client = Client(enforce_csrf_checks=True) response = client.post('/auth-token/login/', json.dumps({'username': self.username}), 'application/json') - self.assertEqual(response.status_code, 400) + self.assertEqual(response.status_code, 401) def test_token_login_form(self): """Ensure token login view using form POST works.""" -- cgit v1.2.3 From e311b763e193b41c6a679ddbcf813702691145a0 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Fri, 30 Nov 2012 01:34:46 +0200 Subject: add traverse_related feature + tests (fixes issue#461) --- rest_framework/serializers.py | 14 ++++++++++--- rest_framework/tests/models.py | 25 ++++++++++++----------- rest_framework/tests/serializer.py | 41 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 15 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4519ab05..e63f4783 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -272,10 +272,18 @@ class BaseSerializer(Field): Override default so that we can apply ModelSerializer as a nested field to relationships. """ - obj = getattr(obj, self.source or field_name) - if is_simple_callable(obj): - obj = obj() + if self.source: + value = obj + for component in self.source.split('.'): + value = getattr(value, component) + if is_simple_callable(value): + value = value() + obj = value + else: + value = getattr(obj, field_name) + if is_simple_callable(value): + obj = value() # If the object has an "all" method, assume it's a relationship if is_simple_callable(getattr(obj, 'all', None)): diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index c35861c6..76435df8 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -124,8 +124,21 @@ class ActionItem(RESTFrameworkModel): # Models for reverse relations +class Person(RESTFrameworkModel): + name = models.CharField(max_length=10) + age = models.IntegerField(null=True, blank=True) + + @property + def info(self): + return { + 'name': self.name, + 'age': self.age, + } + + class BlogPost(RESTFrameworkModel): title = models.CharField(max_length=100) + writer = models.ForeignKey(Person, null=True, blank=True) def get_first_comment(self): return self.blogpostcomment_set.all()[0] @@ -145,18 +158,6 @@ class Photo(RESTFrameworkModel): album = models.ForeignKey(Album) -class Person(RESTFrameworkModel): - name = models.CharField(max_length=10) - age = models.IntegerField(null=True, blank=True) - - @property - def info(self): - return { - 'name': self.name, - 'age': self.age, - } - - # Model for issue #324 class BlankFieldModel(RESTFrameworkModel): title = models.CharField(max_length=100, blank=True) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 61a05da1..b16f2772 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -560,6 +560,47 @@ class ManyRelatedTests(TestCase): self.assertEqual(serializer.data, expected) +class RelatedTraversalTest(TestCase): + def test_nested_traversal(self): + user = Person.objects.create(name="django") + post = BlogPost.objects.create(title="Test blog post", writer=user) + post.blogpostcomment_set.create(text="I love this blog post") + + from rest_framework.tests.models import BlogPostComment + + class PersonSerializer(serializers.ModelSerializer): + class Meta: + model = Person + fields = ("name", "age") + + class BlogPostCommentSerializer(serializers.ModelSerializer): + class Meta: + model = BlogPostComment + fields = ("text", "post_owner") + + text = serializers.CharField() + post_owner = PersonSerializer(source='blog_post.writer') + + class BlogPostSerializer(serializers.Serializer): + title = serializers.CharField() + comments = BlogPostCommentSerializer(source='blogpostcomment_set') + + serializer = BlogPostSerializer(instance=post) + + expected = { + 'title': 'Test blog post', + 'comments': [{ + 'text': 'I hate this blog post', + 'post_owner': { + "name": "django", + "age": None + } + }] + } + + self.assertEqual(serializer.data, expected) + + class SerializerMethodFieldTests(TestCase): def setUp(self): -- cgit v1.2.3 From 1c1bd3fc5d7e65ae8c16e9946be87956c96a1723 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Fri, 30 Nov 2012 01:37:21 +0200 Subject: fix test response --- rest_framework/tests/serializer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index b16f2772..26a7d6bf 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -588,11 +588,11 @@ class RelatedTraversalTest(TestCase): serializer = BlogPostSerializer(instance=post) expected = { - 'title': 'Test blog post', + 'title': u'Test blog post', 'comments': [{ - 'text': 'I hate this blog post', + 'text': u'I love this blog post', 'post_owner': { - "name": "django", + "name": u"django", "age": None } }] -- cgit v1.2.3 From 3867d9deb18d132ec5e0325370c77c2cf9aa0215 Mon Sep 17 00:00:00 2001 From: Michael Richards Date: Tue, 4 Dec 2012 11:07:31 -0800 Subject: Added support for 'true'/'false' as valid boolean data --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 482a3d48..ff39fac4 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -693,9 +693,9 @@ class BooleanField(WritableField): default = False def from_native(self, value): - if value in ('t', 'True', '1'): + if value in ('true', 't', 'True', '1'): return True - if value in ('f', 'False', '0'): + if value in ('false', 'f', 'False', '0'): return False return bool(value) -- cgit v1.2.3