From 7d9125bcb69950e54bb9c2ca61f59403c1018178 Mon Sep 17 00:00:00 2001 From: Mjumbe Wawatu Poe Date: Fri, 7 Sep 2012 17:05:21 -0400 Subject: [PATCH 1/3] Fix Django 1.3 compatibility --- djangorestframework/compat.py | 53 +++++++++++++++++++++++++++ djangorestframework/fields.py | 6 +-- djangorestframework/settings.py | 5 ++- djangorestframework/utils/encoders.py | 4 +- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index 7ced70c57..f21dc4ef6 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -366,6 +366,59 @@ else: return self._accept(request) +# timezone support is new in Django 1.4 +try: + from django.utils import timezone +except ImportError: + timezone = None + +# dateparse is ALSO new in Django 1.4 +try: + from django.utils.dateparse import parse_date, parse_datetime +except ImportError: + import datetime + import re + + date_re = re.compile( + r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})$' + ) + + datetime_re = re.compile( + r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})' + r'[T ](?P\d{1,2}):(?P\d{1,2})' + r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' + r'(?PZ|[+-]\d{1,2}:\d{1,2})?$' + ) + + time_re = re.compile( + r'(?P\d{1,2}):(?P\d{1,2})' + r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' + ) + + def parse_date(value): + match = date_re.match(value) + if match: + kw = dict((k, int(v)) for k, v in match.groupdict().iteritems()) + return datetime.date(**kw) + + def parse_time(value): + match = time_re.match(value) + if match: + kw = match.groupdict() + if kw['microsecond']: + kw['microsecond'] = kw['microsecond'].ljust(6, '0') + kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) + return datetime.time(**kw) + + def parse_datetime(value): + """Parse datetime, but w/o the timezone awareness in 1.4""" + match = datetime_re.match(value) + if match: + kw = match.groupdict() + if kw['microsecond']: + kw['microsecond'] = kw['microsecond'].ljust(6, '0') + kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) + return datetime.datetime(**kw) # Markdown is optional try: diff --git a/djangorestframework/fields.py b/djangorestframework/fields.py index a44eb417b..13b0e37d3 100644 --- a/djangorestframework/fields.py +++ b/djangorestframework/fields.py @@ -8,10 +8,10 @@ from django.core.exceptions import ValidationError from django.conf import settings from django.db import DEFAULT_DB_ALIAS from django.db.models.related import RelatedObject -from django.utils import timezone -from django.utils.dateparse import parse_date, parse_datetime from django.utils.encoding import is_protected_type, smart_unicode from django.utils.translation import ugettext_lazy as _ +from djangorestframework.compat import parse_date, parse_datetime +from djangorestframework.compat import timezone def is_simple_callable(obj): @@ -317,7 +317,7 @@ class DateField(Field): if value is None: return value if isinstance(value, datetime.datetime): - if settings.USE_TZ and timezone.is_aware(value): + if timezone and settings.USE_TZ and timezone.is_aware(value): # Convert aware datetimes to the default time zone # before casting them to dates (#17742). default_timezone = timezone.get_default_timezone() diff --git a/djangorestframework/settings.py b/djangorestframework/settings.py index 8bb035559..e5181f4b4 100644 --- a/djangorestframework/settings.py +++ b/djangorestframework/settings.py @@ -88,7 +88,10 @@ def import_from_string(val, setting): module_path, class_name = '.'.join(parts[:-1]), parts[-1] module = importlib.import_module(module_path) return getattr(module, class_name) - except: + except Exception, e: + import traceback + tb = traceback.format_exc() + import pdb; pdb.set_trace() msg = "Could not import '%s' for API setting '%s'" % (val, setting) raise ImportError(msg) diff --git a/djangorestframework/utils/encoders.py b/djangorestframework/utils/encoders.py index ba7c85538..748760173 100644 --- a/djangorestframework/utils/encoders.py +++ b/djangorestframework/utils/encoders.py @@ -3,8 +3,8 @@ Helper classes for parsers. """ import datetime import decimal -from django.utils import timezone from django.utils import simplejson as json +from djangorestframework.compat import timezone class JSONEncoder(json.JSONEncoder): @@ -25,7 +25,7 @@ class JSONEncoder(json.JSONEncoder): elif isinstance(o, datetime.date): return o.isoformat() elif isinstance(o, datetime.time): - if timezone.is_aware(o): + if timezone and timezone.is_aware(o): raise ValueError("JSON can't represent timezone-aware times.") r = o.isoformat() if o.microsecond: From f729d0eb0b8901f18799bb714c60d54c73a1ec98 Mon Sep 17 00:00:00 2001 From: Mjumbe Wawatu Poe Date: Fri, 7 Sep 2012 18:44:57 -0400 Subject: [PATCH 2/3] Fix Django master support. - Explicitly encode PUT data as multipart, as Django 1.5 no longer does so by default in the test client --- djangorestframework/tests/request.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/djangorestframework/tests/request.py b/djangorestframework/tests/request.py index 2bb90c0ab..ede5d5d79 100644 --- a/djangorestframework/tests/request.py +++ b/djangorestframework/tests/request.py @@ -4,6 +4,7 @@ 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.test import TestCase, Client +from django.test.client import MULTIPART_CONTENT, BOUNDARY, encode_multipart from djangorestframework import status from djangorestframework.authentication import SessionAuthentication @@ -94,7 +95,8 @@ class TestContentParsing(TestCase): """ data = {'qwerty': 'uiop'} parsers = (FormParser, MultiPartParser) - request = factory.put('/', data, parsers=parsers) + request = factory.put('/', encode_multipart(BOUNDARY, data), parsers=parsers, + content_type=MULTIPART_CONTENT) self.assertEqual(request.DATA.items(), data.items()) def test_standard_behaviour_determines_non_form_content_PUT(self): From 9c007a6197ca5125bed774cf3089de7759e755d1 Mon Sep 17 00:00:00 2001 From: Mjumbe Wawatu Poe Date: Fri, 7 Sep 2012 19:14:20 -0400 Subject: [PATCH 3/3] Fix the tests on 1.3 and HEAD In the latest Django master code, RequestFactory.put behaves fundamentally differently than it did pre-1.5. By default, it expects an octet string as opposed to a dictionary that it will encode like a multipart form. So, for 1.5 and on, we have to be explicit about the multipart type and pre-encode the data. However, pre-1.5 Django expects a dictionary if the content type is multipart. So, the cleanest thing to do is explicitly handle the versions independently. --- djangorestframework/tests/request.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/djangorestframework/tests/request.py b/djangorestframework/tests/request.py index ede5d5d79..8b2f66ee0 100644 --- a/djangorestframework/tests/request.py +++ b/djangorestframework/tests/request.py @@ -4,7 +4,6 @@ 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.test import TestCase, Client -from django.test.client import MULTIPART_CONTENT, BOUNDARY, encode_multipart from djangorestframework import status from djangorestframework.authentication import SessionAuthentication @@ -95,8 +94,16 @@ class TestContentParsing(TestCase): """ data = {'qwerty': 'uiop'} parsers = (FormParser, MultiPartParser) - request = factory.put('/', encode_multipart(BOUNDARY, data), parsers=parsers, - content_type=MULTIPART_CONTENT) + + from django import VERSION + + if VERSION >= (1, 5): + from django.test.client import MULTIPART_CONTENT, BOUNDARY, encode_multipart + request = factory.put('/', encode_multipart(BOUNDARY, data), parsers=parsers, + content_type=MULTIPART_CONTENT) + else: + request = factory.put('/', data, parsers=parsers) + self.assertEqual(request.DATA.items(), data.items()) def test_standard_behaviour_determines_non_form_content_PUT(self):