diff options
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | docs/api-guide/fields.md | 8 | ||||
| -rw-r--r-- | docs/api-guide/relations.md | 24 | ||||
| -rw-r--r-- | docs/api-guide/throttling.md | 9 | ||||
| -rw-r--r-- | docs/index.md | 2 | ||||
| -rw-r--r-- | docs/topics/2.2-announcement.md | 9 | ||||
| -rw-r--r-- | docs/topics/credits.md | 6 | ||||
| -rw-r--r-- | docs/topics/release-notes.md | 2 | ||||
| -rw-r--r-- | rest_framework/compat.py | 2 | ||||
| -rw-r--r-- | rest_framework/fields.py | 40 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 1 | ||||
| -rw-r--r-- | rest_framework/tests/fields.py | 48 | ||||
| -rw-r--r-- | rest_framework/views.py | 5 |
13 files changed, 128 insertions, 30 deletions
@@ -26,7 +26,7 @@ There is also a sandbox API you can use for testing purposes, [available here][s # Requirements -* Python (2.6, 2.7, 3.2, 3.3) +* Python (2.6.5+, 2.7, 3.2, 3.3) * Django (1.3, 1.4, 1.5) **Optional:** diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 3f8a36e2..8c28273b 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -199,10 +199,16 @@ If you want to override this behavior, you'll need to declare the `DateTimeField class CommentSerializer(serializers.ModelSerializer): created = serializers.DateTimeField() - + class Meta: model = Comment +## TimeField + +A time representation. + +Corresponds to `django.db.models.fields.TimeField` + ## IntegerField An integer representation. diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index 25fca475..623fe1a9 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -43,7 +43,7 @@ In order to explain the various types of relational fields, we'll use a couple o For example, the following serializer. - class AlbumSerializer(serializer.ModelSerializer): + class AlbumSerializer(serializers.ModelSerializer): tracks = RelatedField(many=True) class Meta: @@ -75,7 +75,7 @@ This field is read only. For example, the following serializer: - class AlbumSerializer(serializer.ModelSerializer): + class AlbumSerializer(serializers.ModelSerializer): tracks = PrimaryKeyRelatedField(many=True, read_only=True) class Meta: @@ -109,7 +109,7 @@ By default this field is read-write, although you can change this behavior using For example, the following serializer: - class AlbumSerializer(serializer.ModelSerializer): + class AlbumSerializer(serializers.ModelSerializer): tracks = HyperlinkedRelatedField(many=True, read_only=True, view_name='track-detail') @@ -149,7 +149,7 @@ By default this field is read-write, although you can change this behavior using For example, the following serializer: - class AlbumSerializer(serializer.ModelSerializer): + class AlbumSerializer(serializers.ModelSerializer): tracks = SlugRelatedField(many=True, read_only=True, slug_field='title') class Meta: @@ -223,12 +223,12 @@ Note that nested relationships are currently read-only. For read-write relation For example, the following serializer: - class TrackSerializer(serializer.ModelSerializer): + class TrackSerializer(serializers.ModelSerializer): class Meta: model = Track fields = ('order', 'title') - class AlbumSerializer(serializer.ModelSerializer): + class AlbumSerializer(serializers.ModelSerializer): tracks = TrackSerializer(many=True) class Meta: @@ -265,7 +265,7 @@ For, example, we could define a relational field, to serialize a track to a cust duration = time.strftime('%M:%S', time.gmtime(value.duration)) return 'Track %d: %s (%s)' % (value.order, value.name, duration) - class AlbumSerializer(serializer.ModelSerializer): + class AlbumSerializer(serializers.ModelSerializer): tracks = TrackListingField(many=True) class Meta: @@ -295,14 +295,14 @@ Note that reverse relationships are not automatically generated by the `ModelSer **The following will not work:** - class AlbumSerializer(serializer.ModelSerializer): + class AlbumSerializer(serializers.ModelSerializer): class Meta: fields = ('tracks', ...) Instead, you must explicitly add it to the serializer. For example: - class AlbumSerializer(serializer.ModelSerializer): - tracks = serializers.PrimaryKeyRelationship(many=True) + class AlbumSerializer(serializers.ModelSerializer): + tracks = serializers.PrimaryKeyRelatedField(many=True) ... By default, the field will uses the same accessor as it's field name to retrieve the relationship, so in this example, `Album` instances would need to have the `tracks` attribute for this relationship to work. @@ -315,8 +315,8 @@ The best way to ensure this is typically to make sure that the relationship on t Alternatively, you can use the `source` argument on the serializer field, to use a different accessor attribute than the field name. For example. - class AlbumSerializer(serializer.ModelSerializer): - tracks = serializers.PrimaryKeyRelationship(many=True, source='track_set') + class AlbumSerializer(serializers.ModelSerializer): + tracks = serializers.PrimaryKeyRelatedField(many=True, source='track_set') See the Django documentation on [reverse relationships][reverse-relationships] for more details. diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 923593bc..1abd49f4 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -6,8 +6,6 @@ > > [Twitter API rate limiting response][cite] -[cite]: https://dev.twitter.com/docs/error-codes-responses - Throttling is similar to [permissions], in that it determines if a request should be authorized. Throttles indicate a temporary state, and are used to control the rate of requests that clients can make to an API. As with permissions, multiple throttles may be used. Your API might have a restrictive throttle for unauthenticated requests, and a less restrictive throttle for authenticated requests. @@ -63,6 +61,10 @@ Or, if you're using the `@api_view` decorator with function based views. } return Response(content) +## Setting up the cache + +The throttle classes provided by REST framework use Django's cache backend. You should make sure that you've set appropriate [cache settings][cache-setting]. The default value of `LocMemCache` backend should be okay for simple setups. See Django's [cache documentation][cache-docs] for more details. + --- # API Reference @@ -162,4 +164,7 @@ The following is an example of a rate throttle, that will randomly throttle 1 in def allow_request(self, request, view): return random.randint(1, 10) == 1 +[cite]: https://dev.twitter.com/docs/error-codes-responses [permissions]: permissions.md +[cache-setting]: https://docs.djangoproject.com/en/dev/ref/settings/#caches +[cache-docs]: https://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache
\ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 32b42419..0188accf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,7 +27,7 @@ There is also a sandbox API you can use for testing purposes, [available here][s REST framework requires the following: -* Python (2.6, 2.7, 3.2, 3.3) +* Python (2.6.5+, 2.7, 3.2, 3.3) * Django (1.3, 1.4, 1.5) The following packages are optional: diff --git a/docs/topics/2.2-announcement.md b/docs/topics/2.2-announcement.md index e24fc615..d7164ce4 100644 --- a/docs/topics/2.2-announcement.md +++ b/docs/topics/2.2-announcement.md @@ -10,6 +10,8 @@ Django 1.6's Python 3 support is expected to be officially labeled as 'productio If you want to start ensuring that your own projects are Python 3 ready, we can highly recommend Django's [Porting to Python 3][porting-python-3] documentation. +Django REST framework's Python 2.6 support now requires 2.6.5 or above, in line with [Django 1.5's Python compatibility][python-compat]. + ## Deprecation policy We've now introduced an official deprecation policy, which is in line with [Django's deprecation policy][django-deprecation-policy]. This policy will make it easy for you to continue to track the latest, greatest version of REST framework. @@ -30,9 +32,11 @@ As of the 2.2 merge, we've also hit an impressive milestone. The number of comm Our [mailing list][mailing-list] and #restframework IRC channel are also very active, and we've got a really impressive rate of development both on REST framework itself, and on third party packages such as the great [django-rest-framework-docs][django-rest-framework-docs] package from [Marc Gibbons][marcgibbons]. +--- + ## API changes -The 2.2 release makes a few changes to the serializer fields API, in order to make it more consistent, simple, and easier to use. +The 2.2 release makes a few changes to the API, in order to make it more consistent, simple, and easier to use. ### Cleaner to-many related fields @@ -136,7 +140,7 @@ If you're overriding the `BasePermission` class, the old-style signature will co Note also that the usage of the internal APIs for permission checking on the `View` class has been cleaned up slightly, and is now documented and subject to the deprecation policy in all future versions. -## More explicit hyperlink relations behavior +### More explicit hyperlink relations behavior When using a serializer with a `HyperlinkedRelatedField` or `HyperlinkedIdentityField`, the hyperlinks would previously use absolute URLs if the serializer context included a `'request'` key, and fallback to using relative URLs otherwise. This could lead to non-obvious behavior, as it might not be clear why some serializers generated absolute URLs, and others do not. @@ -145,6 +149,7 @@ From version 2.2 onwards, serializers with hyperlinked relationships *always* re [xordoquy]: https://github.com/xordoquy [django-python-3]: https://docs.djangoproject.com/en/dev/faq/install/#can-i-use-django-with-python-3 [porting-python-3]: https://docs.djangoproject.com/en/dev/topics/python3/ +[python-compat]: https://docs.djangoproject.com/en/dev/releases/1.5/#python-compatibility [django-deprecation-policy]: https://docs.djangoproject.com/en/dev/internals/release-process/#internal-release-deprecation-policy [credits]: http://django-rest-framework.org/topics/credits.html [mailing-list]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 1320d4d4..e546548e 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -4,7 +4,7 @@ The following people have helped make REST framework great. * Tom Christie - [tomchristie] * Marko Tibold - [markotibold] -* Paul Bagwell - [pbgwl] +* Paul Miller - [paulmillr] * Sébastien Piquemal - [sebpiq] * Carmen Wick - [cwick] * Alex Ehlke - [aehlke] @@ -103,6 +103,7 @@ The following people have helped make REST framework great. * Fernando Rocha - [fernandogrd] * Xavier Ordoquy - [xordoquy] * Adam Wentz - [floppya] +* Andreas Pelme - [pelme] Many thanks to everyone who's contributed to the project. @@ -141,7 +142,7 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [tomchristie]: https://github.com/tomchristie [markotibold]: https://github.com/markotibold -[pbgwl]: https://github.com/pbgwl +[paulmillr]: https://github.com/paulmillr [sebpiq]: https://github.com/sebpiq [cwick]: https://github.com/cwick [aehlke]: https://github.com/aehlke @@ -240,3 +241,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [fernandogrd]: https://github.com/fernandogrd [xordoquy]: https://github.com/xordoquy [floppya]: https://github.com/floppya +[pelme]: https://github.com/pelme diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 3f3f8786..d5444f72 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -42,6 +42,8 @@ You can determine your currently installed version using `pip freeze`: ### Master +* Added TimeField. +* Serializer fields can be mapped to any method that takes no args, or only takes kwargs which have defaults. * Bugfix: request.DATA should return an empty `QueryDict` with no data, not `None`. * Bugfix: Remove unneeded field validation, which caused extra queries. diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 9636b9c1..3fd865f8 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -349,7 +349,7 @@ except ImportError: # dateparse is ALSO new in Django 1.4 try: - from django.utils.dateparse import parse_date, parse_datetime + from django.utils.dateparse import parse_date, parse_datetime, parse_time except ImportError: import datetime import re diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 327008fb..e1fd1b64 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -18,16 +18,21 @@ from rest_framework.compat import timezone from rest_framework.compat import BytesIO from rest_framework.compat import six from rest_framework.compat import smart_text +from rest_framework.compat import parse_time def is_simple_callable(obj): """ True if the object is a callable that takes no arguments. """ - return ( - (inspect.isfunction(obj) and not inspect.getargspec(obj)[0]) or - (inspect.ismethod(obj) and len(inspect.getargspec(obj)[0]) <= 1) - ) + try: + args, _, _, defaults = inspect.getargspec(obj) + except TypeError: + return False + else: + len_args = len(args) if inspect.isfunction(obj) else len(args) - 1 + len_defaults = len(defaults) if defaults else 0 + return len_args <= len_defaults def get_component(obj, attr_name): @@ -531,6 +536,33 @@ class DateTimeField(WritableField): raise ValidationError(msg) +class TimeField(WritableField): + type_name = 'TimeField' + widget = widgets.TimeInput + form_field_class = forms.TimeField + + default_error_messages = { + 'invalid': _("'%s' value has an invalid format. It must be a valid " + "time in the HH:MM[:ss[.uuuuuu]] format."), + } + empty = None + + def from_native(self, value): + if value in validators.EMPTY_VALUES: + return None + + if isinstance(value, datetime.time): + return value + + try: + parsed = parse_time(value) + assert parsed is not None + return parsed + except ValueError: + msg = self.error_messages['invalid'] % value + raise ValidationError(msg) + + class IntegerField(WritableField): type_name = 'IntegerField' form_field_class = forms.IntegerField diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5d3475d4..b0372ab8 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -517,6 +517,7 @@ class ModelSerializer(Serializer): models.PositiveSmallIntegerField: IntegerField, models.DateTimeField: DateTimeField, models.DateField: DateField, + models.TimeField: TimeField, models.EmailField: EmailField, models.CharField: CharField, models.URLField: URLField, diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/fields.py index b7587bf1..34f61678 100644 --- a/rest_framework/tests/fields.py +++ b/rest_framework/tests/fields.py @@ -2,8 +2,10 @@ General serializer field tests. """ from __future__ import unicode_literals +import datetime from django.db import models from django.test import TestCase +from django.core import validators from rest_framework import serializers @@ -26,7 +28,16 @@ class CharPrimaryKeyModelSerializer(serializers.ModelSerializer): model = CharPrimaryKeyModel -class ReadOnlyFieldTests(TestCase): +class TimeFieldModel(models.Model): + clock = models.TimeField() + + +class TimeFieldModelSerializer(serializers.ModelSerializer): + class Meta: + model = TimeFieldModel + + +class BasicFieldTests(TestCase): def test_auto_now_fields_read_only(self): """ auto_now and auto_now_add fields should be read_only by default. @@ -47,3 +58,38 @@ class ReadOnlyFieldTests(TestCase): """ serializer = CharPrimaryKeyModelSerializer() self.assertEquals(serializer.fields['id'].read_only, False) + + def test_TimeField_from_native(self): + f = serializers.TimeField() + result = f.from_native('12:34:56.987654') + + self.assertEqual(datetime.time(12, 34, 56, 987654), result) + + def test_TimeField_from_native_datetime_time(self): + """ + Make sure from_native() accepts a datetime.time instance. + """ + f = serializers.TimeField() + result = f.from_native(datetime.time(12, 34, 56)) + self.assertEqual(result, datetime.time(12, 34, 56)) + + def test_TimeField_from_native_empty(self): + f = serializers.TimeField() + result = f.from_native('') + self.assertEqual(result, None) + + def test_TimeField_from_native_invalid_time(self): + f = serializers.TimeField() + + try: + f.from_native('12:69:12') + except validators.ValidationError as e: + self.assertEqual(e.messages, ["'12:69:12' value has an invalid " + "format. It must be a valid time " + "in the HH:MM[:ss[.uuuuuu]] format."]) + else: + self.fail("ValidationError was not properly raised") + + def test_TimeFieldModelSerializer(self): + serializer = TimeFieldModelSerializer() + self.assertTrue(isinstance(serializer.fields['clock'], serializers.TimeField)) diff --git a/rest_framework/views.py b/rest_framework/views.py index 55ad8cf3..fa742582 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -13,7 +13,6 @@ from rest_framework.response import Response from rest_framework.request import Request from rest_framework.settings import api_settings import re -import warnings def _remove_trailing_string(content, trailing): @@ -212,13 +211,13 @@ class APIView(View): def get_parsers(self): """ - Instantiates and returns the list of renderers that this view can use. + Instantiates and returns the list of parsers that this view can use. """ return [parser() for parser in self.parser_classes] def get_authenticators(self): """ - Instantiates and returns the list of renderers that this view can use. + Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes] |
