diff --git a/docs/cookbook.rst b/docs/cookbook.rst index 8afb19b21..fd8ef580f 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -29,12 +29,14 @@ A common pattern is needing to limit a queryset by something that changes per-request, for instance the date/time. You can accomplish this by lightly modifying ``get_object_list``:: + from tastypie.utils import now + class MyResource(ModelResource): class Meta: queryset = MyObject.objects.all() def get_object_list(self, request): - return super(MyResource, self).get_object_list(request).filter(start_date__gte=datetime.datetime.now) + return super(MyResource, self).get_object_list(request).filter(start_date__gte=now) Using Your ``Resource`` In Regular Views diff --git a/docs/fields.rst b/docs/fields.rst index 3d16baed3..a7b4e1839 100644 --- a/docs/fields.rst +++ b/docs/fields.rst @@ -15,8 +15,7 @@ Quick Start For the impatient:: - import datetime - from tastypie import fields + from tastypie import fields, utils from tastypie.resources import Resource from myapp.api.resources import ProfileResource, NoteResource @@ -24,7 +23,7 @@ For the impatient:: class PersonResource(Resource): name = fields.CharField(attribute='name') age = fields.IntegerField(attribute='years_old', null=True) - created = fields.DateTimeField(readonly=True, help_text='When the person was created', default=datetime.datetime.now) + created = fields.DateTimeField(readonly=True, help_text='When the person was created', default=utils.now) is_active = fields.BooleanField(default=True) profile = fields.ToOneField(ProfileResource, 'profile') notes = fields.ToManyField(NoteResource, 'notes', full=True) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index df82fa822..d0f9d43b0 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -25,7 +25,7 @@ of the code that are Tastypie-specific in any kind of depth. For example purposes, we'll be adding an API to a simple blog application. Here is ``myapp/models.py``:: - import datetime + from tastypie.utils import now from django.contrib.auth.models import User from django.db import models from django.template.defaultfilters import slugify @@ -33,7 +33,7 @@ Here is ``myapp/models.py``:: class Entry(models.Model): user = models.ForeignKey(User) - pub_date = models.DateTimeField(default=datetime.datetime.now) + pub_date = models.DateTimeField(default=now) title = models.CharField(max_length=200) slug = models.SlugField() body = models.TextField() diff --git a/requirements.txt b/requirements.txt index 1d76e2b25..660ab706e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,5 @@ mimeparse>=0.1.3 python-dateutil==1.5 lxml PyYAML -python_digest +python-digest biplist diff --git a/tastypie/fields.py b/tastypie/fields.py index a7d814a2d..c8539f7ad 100644 --- a/tastypie/fields.py +++ b/tastypie/fields.py @@ -6,7 +6,7 @@ from django.utils import datetime_safe, importlib from tastypie.bundle import Bundle from tastypie.exceptions import ApiFieldError, NotFound -from tastypie.utils import dict_strip_unicode_keys +from tastypie.utils import dict_strip_unicode_keys, make_aware class NOT_PROVIDED: @@ -322,7 +322,7 @@ def hydrate(self, bundle): if value and not hasattr(value, 'year'): try: # Try to rip a date/datetime out of it. - value = parse(value) + value = make_aware(parse(value)) if hasattr(value, 'hour'): value = value.date() @@ -348,7 +348,7 @@ def convert(self, value): if match: data = match.groupdict() - return datetime_safe.datetime(int(data['year']), int(data['month']), int(data['day']), int(data['hour']), int(data['minute']), int(data['second'])) + return make_aware(datetime_safe.datetime(int(data['year']), int(data['month']), int(data['day']), int(data['hour']), int(data['minute']), int(data['second']))) else: raise ApiFieldError("Datetime provided to '%s' field doesn't appear to be a valid datetime string: '%s'" % (self.instance_name, value)) @@ -360,7 +360,7 @@ def hydrate(self, bundle): if value and not hasattr(value, 'year'): try: # Try to rip a date/datetime out of it. - value = parse(value) + value = make_aware(parse(value)) except ValueError: pass diff --git a/tastypie/migrations/0001_initial.py b/tastypie/migrations/0001_initial.py index 0f9f48bf6..41a4d4b2b 100644 --- a/tastypie/migrations/0001_initial.py +++ b/tastypie/migrations/0001_initial.py @@ -4,6 +4,8 @@ from south.v2 import SchemaMigration from django.db import models +from tastypie.utils import now + class Migration(SchemaMigration): def forwards(self, orm): @@ -23,7 +25,7 @@ def forwards(self, orm): ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='api_key', unique=True, to=orm['auth.User'])), ('key', self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True)), - ('created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), + ('created', self.gf('django.db.models.fields.DateTimeField')(default=now)), )) db.send_create_signal('tastypie', ['ApiKey']) @@ -53,7 +55,7 @@ def backwards(self, orm): }, 'auth.user': { 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'tastypie.utils.now'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), @@ -61,7 +63,7 @@ def backwards(self, orm): 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'tastypie.utils.now'}), 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), @@ -84,7 +86,7 @@ def backwards(self, orm): }, 'tastypie.apikey': { 'Meta': {'object_name': 'ApiKey'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'tastypie.utils.now'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'api_key'", 'unique': 'True', 'to': "orm['auth.User']"}) diff --git a/tastypie/models.py b/tastypie/models.py index 3c7f6a6e5..1d8db698f 100644 --- a/tastypie/models.py +++ b/tastypie/models.py @@ -3,6 +3,8 @@ import time from django.conf import settings from django.db import models +from tastypie.utils import now + try: from hashlib import sha1 except ImportError: @@ -33,8 +35,8 @@ def save(self, *args, **kwargs): class ApiKey(models.Model): user = models.OneToOneField(User, related_name='api_key') key = models.CharField(max_length=256, blank=True, default='') - created = models.DateTimeField(default=datetime.datetime.now) - + created = models.DateTimeField(default=now) + def __unicode__(self): return u"%s for %s" % (self.key, self.user) diff --git a/tastypie/resources.py b/tastypie/resources.py index bd69040c7..17a26b43e 100644 --- a/tastypie/resources.py +++ b/tastypie/resources.py @@ -2008,7 +2008,6 @@ def convert_post_to_VERB(request, verb): request.META['REQUEST_METHOD'] = 'POST' request._load_post_and_files() request.META['REQUEST_METHOD'] = verb - setattr(request, verb, request.POST) return request diff --git a/tastypie/serializers.py b/tastypie/serializers.py index 19baab15f..86010ce4d 100644 --- a/tastypie/serializers.py +++ b/tastypie/serializers.py @@ -7,7 +7,7 @@ from django.utils.encoding import force_unicode from tastypie.bundle import Bundle from tastypie.exceptions import UnsupportedFormat -from tastypie.utils import format_datetime, format_date, format_time +from tastypie.utils import format_datetime, format_date, format_time, make_naive try: import lxml from lxml.etree import parse as parse_xml @@ -120,6 +120,7 @@ def format_datetime(self, data): Default is ``iso-8601``, which looks like "2010-12-16T03:02:14". """ + data = make_naive(data) if self.datetime_formatting == 'rfc-2822': return format_datetime(data) diff --git a/tastypie/utils/__init__.py b/tastypie/utils/__init__.py index 72e6e9a11..9c2de8fc8 100644 --- a/tastypie/utils/__init__.py +++ b/tastypie/utils/__init__.py @@ -2,3 +2,4 @@ from tastypie.utils.formatting import mk_datetime, format_datetime, format_date, format_time from tastypie.utils.urls import trailing_slash from tastypie.utils.validate_jsonp import is_valid_jsonp_callback_value +from tastypie.utils.timezone import now, make_aware, make_naive, aware_date, aware_datetime diff --git a/tastypie/utils/formatting.py b/tastypie/utils/formatting.py index 438c35ecf..74da2bba5 100644 --- a/tastypie/utils/formatting.py +++ b/tastypie/utils/formatting.py @@ -1,6 +1,7 @@ import email import datetime from django.utils import dateformat +from tastypie.utils.timezone import make_aware, make_naive, aware_datetime # Try to use dateutil for maximum date-parsing niceness. Fall back to # hard-coded RFC2822 parsing if that's not possible. @@ -8,13 +9,13 @@ from dateutil.parser import parse as mk_datetime except ImportError: def mk_datetime(string): - return datetime.datetime.fromtimestamp(time.mktime(email.utils.parsedate(string))) + return make_aware(datetime.datetime.fromtimestamp(time.mktime(email.utils.parsedate(string)))) def format_datetime(dt): """ RFC 2822 datetime formatter """ - return dateformat.format(dt, 'r') + return dateformat.format(make_naive(dt), 'r') def format_date(d): """ @@ -22,7 +23,7 @@ def format_date(d): """ # workaround because Django's dateformat utility requires a datetime # object (not just date) - dt = datetime.datetime(d.year, d.month, d.day, 0, 0, 0) + dt = aware_datetime(d.year, d.month, d.day, 0, 0, 0) return dateformat.format(dt, 'j M Y') def format_time(t): @@ -30,5 +31,5 @@ def format_time(t): RFC 2822 time formatter """ # again, workaround dateformat input requirement - dt = datetime.datetime(2000, 1, 1, t.hour, t.minute, t.second) + dt = aware_datetime(2000, 1, 1, t.hour, t.minute, t.second) return dateformat.format(dt, 'H:i:s O') diff --git a/tastypie/utils/timezone.py b/tastypie/utils/timezone.py new file mode 100644 index 000000000..7ab55ddb9 --- /dev/null +++ b/tastypie/utils/timezone.py @@ -0,0 +1,30 @@ +import datetime +from django.conf import settings + +try: + from django.utils import timezone + + def make_aware(value): + if getattr(settings, "USE_TZ", False): + default_tz = timezone.get_default_timezone() + value = timezone.make_aware(value, default_tz) + return value + + def make_naive(value): + if getattr(settings, "USE_TZ", False) and timezone.is_aware(value): + default_tz = timezone.get_default_timezone() + value = timezone.make_naive(value, default_tz) + return value + + def now(): + return timezone.localtime(timezone.now()) + +except ImportError: + now = datetime.datetime.now + make_aware = make_naive = lambda x: x + +def aware_date(*args, **kwargs): + return make_aware(datetime.date(*args, **kwargs)) + +def aware_datetime(*args, **kwargs): + return make_aware(datetime.datetime(*args, **kwargs)) diff --git a/tests/alphanumeric/models.py b/tests/alphanumeric/models.py index c55d225fd..0bfdcac77 100644 --- a/tests/alphanumeric/models.py +++ b/tests/alphanumeric/models.py @@ -1,16 +1,17 @@ import datetime from django.db import models +from tastypie.utils import now class Product(models.Model): artnr = models.CharField(max_length=8, primary_key=True) name = models.CharField(max_length=32, null=False, blank=True, default='') - created = models.DateTimeField(default=datetime.datetime.now) - updated = models.DateTimeField(default=datetime.datetime.now) - + created = models.DateTimeField(default=now) + updated = models.DateTimeField(default=now) + def __unicode__(self): return "%s - %s" % (self.artnr, self.name) - + def save(self, *args, **kwargs): - self.updated = datetime.datetime.now() + self.updated = now() return super(Product, self).save(*args, **kwargs) diff --git a/tests/basic/models.py b/tests/basic/models.py index 4f6f60c45..523bbd997 100644 --- a/tests/basic/models.py +++ b/tests/basic/models.py @@ -1,7 +1,7 @@ import datetime from django.contrib.auth.models import User from django.db import models - +from tastypie.utils import now class Note(models.Model): user = models.ForeignKey(User, related_name='notes') @@ -9,14 +9,14 @@ class Note(models.Model): slug = models.SlugField() content = models.TextField() is_active = models.BooleanField(default=True) - created = models.DateTimeField(default=datetime.datetime.now) - updated = models.DateTimeField(default=datetime.datetime.now) - + created = models.DateTimeField(default=now) + updated = models.DateTimeField(default=now) + def __unicode__(self): return self.title def save(self, *args, **kwargs): - self.updated = datetime.datetime.now() + self.updated = now() return super(Note, self).save(*args, **kwargs) class AnnotatedNote(models.Model): diff --git a/tests/complex/models.py b/tests/complex/models.py index 1b2197879..f94504f2f 100644 --- a/tests/complex/models.py +++ b/tests/complex/models.py @@ -9,6 +9,8 @@ from django.db.models import signals, get_models from django.conf import settings +from tastypie.utils import now + class Post(models.Model): user = models.ForeignKey(User, related_name='notes') @@ -16,15 +18,15 @@ class Post(models.Model): slug = models.SlugField() content = models.TextField() is_active = models.BooleanField(default=True) - created = models.DateTimeField(default=datetime.datetime.now) - updated = models.DateTimeField(default=datetime.datetime.now) + created = models.DateTimeField(default=now) + updated = models.DateTimeField(default=now) comments = generic.GenericRelation(Comment, content_type_field="content_type", object_id_field="object_pk") def __unicode__(self): return self.title def save(self, *args, **kwargs): - self.updated = datetime.datetime.now() + self.updated = now() return super(Post, self).save(*args, **kwargs) diff --git a/tests/core/models.py b/tests/core/models.py index 35db058eb..35b45d82f 100644 --- a/tests/core/models.py +++ b/tests/core/models.py @@ -1,6 +1,7 @@ import datetime from django.contrib.auth.models import User from django.db import models +from tastypie.utils import now, aware_datetime class Note(models.Model): @@ -9,19 +10,19 @@ class Note(models.Model): slug = models.SlugField() content = models.TextField(blank=True) is_active = models.BooleanField(default=True) - created = models.DateTimeField(default=datetime.datetime.now) - updated = models.DateTimeField(default=datetime.datetime.now) - + created = models.DateTimeField(default=now) + updated = models.DateTimeField(default=now) + def __unicode__(self): return self.title def save(self, *args, **kwargs): - self.updated = datetime.datetime.now() + self.updated = now() return super(Note, self).save(*args, **kwargs) def what_time_is_it(self): - return datetime.datetime(2010, 4, 1, 0, 48) - + return aware_datetime(2010, 4, 1, 0, 48) + def get_absolute_url(self): return '/some/fake/path/%s/' % self.pk @@ -34,8 +35,8 @@ class Subject(models.Model): notes = models.ManyToManyField(Note, related_name='subjects') name = models.CharField(max_length=255) url = models.URLField(verify_exists=False) - created = models.DateTimeField(default=datetime.datetime.now) - + created = models.DateTimeField(default=now) + def __unicode__(self): return self.name diff --git a/tests/core/tests/fields.py b/tests/core/tests/fields.py index e057db293..c4f33764f 100644 --- a/tests/core/tests/fields.py +++ b/tests/core/tests/fields.py @@ -9,6 +9,8 @@ from tastypie.resources import ModelResource from core.models import Note, Subject, MediaBit +from tastypie.utils import aware_datetime, aware_date + class ApiFieldTestCase(TestCase): fixtures = ['note_testdata.json'] @@ -75,7 +77,7 @@ def test_dehydrate(self): # Correct callable attribute. field_6 = ApiField(attribute='what_time_is_it', default=True) - self.assertEqual(field_6.dehydrate(bundle), datetime.datetime(2010, 4, 1, 0, 48)) + self.assertEqual(field_6.dehydrate(bundle), aware_datetime(2010, 4, 1, 0, 48)) def test_convert(self): field_1 = ApiField() @@ -390,7 +392,7 @@ def test_dehydrate(self): bundle = Bundle(obj=note) field_1 = TimeField(attribute='created') - self.assertEqual(field_1.dehydrate(bundle), datetime.datetime(2010, 3, 30, 20, 5)) + self.assertEqual(field_1.dehydrate(bundle), aware_datetime(2010, 3, 30, 20, 5)) field_2 = TimeField(default=datetime.time(23, 5, 58)) self.assertEqual(field_2.dehydrate(bundle), datetime.time(23, 5, 58)) @@ -452,7 +454,7 @@ def test_dehydrate(self): bundle = Bundle(obj=note) field_1 = DateField(attribute='created') - self.assertEqual(field_1.dehydrate(bundle), datetime.datetime(2010, 3, 30, 20, 5)) + self.assertEqual(field_1.dehydrate(bundle), aware_datetime(2010, 3, 30, 20, 5)) field_2 = DateField(default=datetime.date(2010, 4, 1)) self.assertEqual(field_2.dehydrate(bundle), datetime.date(2010, 4, 1)) @@ -517,14 +519,14 @@ def test_dehydrate(self): bundle = Bundle(obj=note) field_1 = DateTimeField(attribute='created') - self.assertEqual(field_1.dehydrate(bundle), datetime.datetime(2010, 3, 30, 20, 5)) + self.assertEqual(field_1.dehydrate(bundle), aware_datetime(2010, 3, 30, 20, 5)) - field_2 = DateTimeField(default=datetime.datetime(2010, 4, 1, 1, 7)) - self.assertEqual(field_2.dehydrate(bundle), datetime.datetime(2010, 4, 1, 1, 7)) + field_2 = DateTimeField(default=aware_datetime(2010, 4, 1, 1, 7)) + self.assertEqual(field_2.dehydrate(bundle), aware_datetime(2010, 4, 1, 1, 7)) note.created_string = '2010-04-02 01:11:00' field_3 = DateTimeField(attribute='created_string') - self.assertEqual(field_3.dehydrate(bundle), datetime.datetime(2010, 4, 2, 1, 11)) + self.assertEqual(field_3.dehydrate(bundle), aware_datetime(2010, 4, 2, 1, 11)) def test_hydrate(self): note = Note.objects.get(pk=1) @@ -534,19 +536,19 @@ def test_hydrate(self): }) field_1 = DateTimeField(attribute='created') field_1.instance_name = 'datetime' - self.assertEqual(field_1.hydrate(bundle_1), datetime.datetime(2010, 5, 12, 10, 36, 28)) + self.assertEqual(field_1.hydrate(bundle_1), aware_datetime(2010, 5, 12, 10, 36, 28)) bundle_2 = Bundle() - field_2 = DateTimeField(default=datetime.datetime(2010, 4, 1, 2, 0)) + field_2 = DateTimeField(default=aware_datetime(2010, 4, 1, 2, 0)) field_2.instance_name = 'datetime' - self.assertEqual(field_2.hydrate(bundle_2), datetime.datetime(2010, 4, 1, 2, 0)) + self.assertEqual(field_2.hydrate(bundle_2), aware_datetime(2010, 4, 1, 2, 0)) bundle_3 = Bundle(data={ 'datetime': 'Tue, 30 Mar 2010 20:05:00 -0500', }) field_3 = DateTimeField(attribute='created_string') field_3.instance_name = 'datetime' - self.assertEqual(field_3.hydrate(bundle_3), datetime.datetime(2010, 3, 30, 20, 5, tzinfo=tzoffset(None, -18000))) + self.assertEqual(field_3.hydrate(bundle_3), aware_datetime(2010, 3, 30, 20, 5, tzinfo=tzoffset(None, -18000))) bundle_4 = Bundle(data={ 'datetime': None, diff --git a/tests/core/tests/resources.py b/tests/core/tests/resources.py index 795585012..149b0411f 100644 --- a/tests/core/tests/resources.py +++ b/tests/core/tests/resources.py @@ -22,6 +22,7 @@ from tastypie.resources import Resource, ModelResource, ALL, ALL_WITH_RELATIONS, convert_post_to_put, convert_post_to_patch from tastypie.serializers import Serializer from tastypie.throttle import CacheThrottle +from tastypie.utils import aware_datetime, make_naive from tastypie.validation import Validation, FormValidation from core.models import Note, Subject, MediaBit from core.tests.mocks import MockRequest @@ -58,7 +59,7 @@ def dehydrate_date_joined(self, bundle): if bundle.data.get('date_joined') is not None: return bundle.data.get('date_joined') - return datetime.datetime(2010, 3, 27, 22, 30, 0) + return aware_datetime(2010, 3, 27, 22, 30, 0) def hydrate_date_joined(self, bundle): bundle.obj.date_joined = bundle.data['date_joined'] @@ -167,7 +168,7 @@ def test_to_put(self): } # Make Django happy. request._read_started = False - request._raw_post_data = '' + request._raw_post_data = request._body = '' modified = convert_post_to_put(request) self.assertEqual(modified.method, 'PUT') @@ -188,7 +189,7 @@ def test_to_patch(self): } # Make Django happy. request._read_started = False - request._raw_post_data = '' + request._raw_post_data = request._body = '' modified = convert_post_to_patch(request) self.assertEqual(modified.method, 'PATCH') @@ -283,7 +284,7 @@ def test_full_dehydrate(self): test_object_1 = TestObject() test_object_1.name = 'Daniel' test_object_1.view_count = 12 - test_object_1.date_joined = datetime.datetime(2010, 3, 30, 9, 0, 0) + test_object_1.date_joined = aware_datetime(2010, 3, 30, 9, 0, 0) test_object_1.foo = "Hi, I'm ignored." basic = BasicResource() @@ -316,7 +317,7 @@ def test_full_dehydrate(self): test_object_3 = TestObject() test_object_3.name = 'Joe' test_object_3.view_count = 5 - test_object_3.created = datetime.datetime(2010, 3, 29, 11, 0, 0) + test_object_3.created = aware_datetime(2010, 3, 29, 11, 0, 0) test_object_3.is_active = False test_object_3.bar = "But sometimes I'm not ignored!" another_1 = AnotherBasicResource() @@ -338,7 +339,7 @@ def test_full_hydrate(self): basic_bundle_1 = Bundle(data={ 'name': 'Daniel', 'view_count': 6, - 'date_joined': datetime.datetime(2010, 2, 15, 12, 0, 0) + 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0) }) # Now load up the data. @@ -346,16 +347,16 @@ def test_full_hydrate(self): self.assertEqual(hydrated.data['name'], 'Daniel') self.assertEqual(hydrated.data['view_count'], 6) - self.assertEqual(hydrated.data['date_joined'], datetime.datetime(2010, 2, 15, 12, 0, 0)) + self.assertEqual(hydrated.data['date_joined'], aware_datetime(2010, 2, 15, 12, 0, 0)) self.assertEqual(hydrated.obj.name, 'Daniel') self.assertEqual(hydrated.obj.view_count, 6) - self.assertEqual(hydrated.obj.date_joined, datetime.datetime(2010, 2, 15, 12, 0, 0)) + self.assertEqual(hydrated.obj.date_joined, aware_datetime(2010, 2, 15, 12, 0, 0)) another = AnotherBasicResource() another_bundle_1 = Bundle(data={ 'name': 'Daniel', 'view_count': 6, - 'date_joined': datetime.datetime(2010, 2, 15, 12, 0, 0), + 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0), 'aliases': ['test', 'test1'], 'meta': {'foo': 'bar'}, 'owed': '12.53', @@ -366,19 +367,19 @@ def test_full_hydrate(self): self.assertEqual(hydrated.data['name'], 'Daniel') self.assertEqual(hydrated.data['view_count'], 6) - self.assertEqual(hydrated.data['date_joined'], datetime.datetime(2010, 2, 15, 12, 0, 0)) + self.assertEqual(hydrated.data['date_joined'], aware_datetime(2010, 2, 15, 12, 0, 0)) self.assertEqual(hydrated.data['aliases'], ['test', 'test1']) self.assertEqual(hydrated.data['meta'], {'foo': 'bar'}) self.assertEqual(hydrated.data['owed'], '12.53') self.assertEqual(hydrated.obj.name, 'Daniel') self.assertEqual(hydrated.obj.view_count, 6) - self.assertEqual(hydrated.obj.date_joined, datetime.datetime(2010, 2, 15, 12, 0, 0)) + self.assertEqual(hydrated.obj.date_joined, aware_datetime(2010, 2, 15, 12, 0, 0)) self.assertEqual(hasattr(hydrated.obj, 'bar'), False) another_bundle_2 = Bundle(data={ 'name': 'Daniel', 'view_count': 6, - 'date_joined': datetime.datetime(2010, 2, 15, 12, 0, 0), + 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0), 'bar': True, }) @@ -387,10 +388,10 @@ def test_full_hydrate(self): self.assertEqual(hydrated.data['name'], 'Daniel') self.assertEqual(hydrated.data['view_count'], 6) - self.assertEqual(hydrated.data['date_joined'], datetime.datetime(2010, 2, 15, 12, 0, 0)) + self.assertEqual(hydrated.data['date_joined'], aware_datetime(2010, 2, 15, 12, 0, 0)) self.assertEqual(hydrated.obj.name, 'Daniel') self.assertEqual(hydrated.obj.view_count, 6) - self.assertEqual(hydrated.obj.date_joined, datetime.datetime(2010, 2, 15, 12, 0, 0)) + self.assertEqual(hydrated.obj.date_joined, aware_datetime(2010, 2, 15, 12, 0, 0)) self.assertEqual(hydrated.obj.bar, 'O HAI BAR!') # Test that a nullable value with a previous non-null value @@ -1514,12 +1515,12 @@ def test_get_list(self): title='Another fresh note.', slug='another-fresh-note', content='Whee!', - created=datetime.datetime(2010, 7, 21, 11, 23), - updated=datetime.datetime(2010, 7, 21, 11, 23), + created=aware_datetime(2010, 7, 21, 11, 23), + updated=aware_datetime(2010, 7, 21, 11, 23), ) resp = resource.get_list(request) self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content, '{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_count": 5}, "objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": "1", "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": "2", "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}, {"content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", "created": "2010-04-01T20:05:00", "id": "4", "is_active": true, "resource_uri": "/api/v1/notes/4/", "slug": "recent-volcanic-activity", "title": "Recent Volcanic Activity.", "updated": "2010-04-01T20:05:00"}, {"content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", "created": "2010-04-02T10:05:00", "id": "6", "is_active": true, "resource_uri": "/api/v1/notes/6/", "slug": "grannys-gone", "title": "Granny\'s Gone", "updated": "2010-04-02T10:05:00"}, {"content": "Whee!", "created": "2010-07-21T11:23:00", "id": "7", "is_active": true, "resource_uri": "/api/v1/notes/7/", "slug": "another-fresh-note", "title": "Another fresh note.", "updated": "%s"}]}' % new_note.updated.isoformat()) + self.assertEqual(resp.content, '{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_count": 5}, "objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": "1", "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": "2", "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}, {"content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", "created": "2010-04-01T20:05:00", "id": "4", "is_active": true, "resource_uri": "/api/v1/notes/4/", "slug": "recent-volcanic-activity", "title": "Recent Volcanic Activity.", "updated": "2010-04-01T20:05:00"}, {"content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", "created": "2010-04-02T10:05:00", "id": "6", "is_active": true, "resource_uri": "/api/v1/notes/6/", "slug": "grannys-gone", "title": "Granny\'s Gone", "updated": "2010-04-02T10:05:00"}, {"content": "Whee!", "created": "2010-07-21T11:23:00", "id": "7", "is_active": true, "resource_uri": "/api/v1/notes/7/", "slug": "another-fresh-note", "title": "Another fresh note.", "updated": "%s"}]}' % make_naive(new_note.updated).isoformat()) # Regression - Ensure that the limit on the Resource gets used if # no other limit is requested. @@ -1669,7 +1670,7 @@ def test_patch_list(self): request._read_started = False self.assertEqual(Note.objects.count(), 6) - request._raw_post_data = '{"objects": [{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back-again", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}], "deleted_objects": ["/api/v1/notes/1/"]}' + request._raw_post_data = request._body = '{"objects": [{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00", "is_active": true, "slug": "cat-is-back-again", "title": "The Cat Is Back", "updated": "2010-04-03 20:05:00"}], "deleted_objects": ["/api/v1/notes/1/"]}' resp = resource.patch_list(request) self.assertEqual(resp.status_code, 202) @@ -1686,7 +1687,7 @@ def test_patch_detail(self): request.GET = {'format': 'json'} request.method = 'PATCH' request._read_started = False - request._raw_post_data = '{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00"}' + request._raw_post_data = request._body = '{"content": "The cat is back. The dog coughed him up out back.", "created": "2010-04-03 20:05:00"}' resp = resource.patch_detail(request, pk=10) self.assertEqual(resp.status_code, 404) @@ -1696,9 +1697,9 @@ def test_patch_detail(self): self.assertEqual(Note.objects.count(), 6) note = Note.objects.get(pk=1) self.assertEqual(note.content, "The cat is back. The dog coughed him up out back.") - self.assertEqual(note.created, datetime.datetime(2010, 4, 3, 20, 5)) + self.assertEqual(note.created, aware_datetime(2010, 4, 3, 20, 5)) - request._raw_post_data = '{"content": "The cat is gone again. I think it was the rabbits that ate him this time."}' + request._raw_post_data = request._body = '{"content": "The cat is gone again. I think it was the rabbits that ate him this time."}' resp = resource.patch_detail(request, pk=1) self.assertEqual(resp.status_code, 202) @@ -1850,16 +1851,16 @@ def test_obj_get(self): note = NoteResource() note_obj = note.obj_get(pk=1) self.assertEqual(note_obj.content, u'This is my very first post using my shiny new API. Pretty sweet, huh?') - self.assertEqual(note_obj.created, datetime.datetime(2010, 3, 30, 20, 5)) + self.assertEqual(note_obj.created, aware_datetime(2010, 3, 30, 20, 5)) self.assertEqual(note_obj.is_active, True) self.assertEqual(note_obj.slug, u'first-post') self.assertEqual(note_obj.title, u'First Post!') - self.assertEqual(note_obj.updated, datetime.datetime(2010, 3, 30, 20, 5)) + self.assertEqual(note_obj.updated, aware_datetime(2010, 3, 30, 20, 5)) custom = VeryCustomNoteResource() custom_obj = custom.obj_get(pk=1) self.assertEqual(custom_obj.content, u'This is my very first post using my shiny new API. Pretty sweet, huh?') - self.assertEqual(custom_obj.created, datetime.datetime(2010, 3, 30, 20, 5)) + self.assertEqual(custom_obj.created, aware_datetime(2010, 3, 30, 20, 5)) self.assertEqual(custom_obj.is_active, True) self.assertEqual(custom_obj.author.username, u'johndoe') self.assertEqual(custom_obj.title, u'First Post!') @@ -1867,7 +1868,7 @@ def test_obj_get(self): related = RelatedNoteResource() related_obj = related.obj_get(pk=1) self.assertEqual(related_obj.content, u'This is my very first post using my shiny new API. Pretty sweet, huh?') - self.assertEqual(related_obj.created, datetime.datetime(2010, 3, 30, 20, 5)) + self.assertEqual(related_obj.created, aware_datetime(2010, 3, 30, 20, 5)) self.assertEqual(related_obj.is_active, True) self.assertEqual(related_obj.author.username, u'johndoe') self.assertEqual(related_obj.title, u'First Post!') @@ -1915,8 +1916,8 @@ def test_get_schema(self): # Patch the ``created/updated`` defaults for testability. old_created = resource.fields['created']._default old_updated = resource.fields['updated']._default - resource.fields['created']._default = datetime.datetime(2011, 9, 24, 0, 2) - resource.fields['updated']._default = datetime.datetime(2011, 9, 24, 0, 2) + resource.fields['created']._default = aware_datetime(2011, 9, 24, 0, 2) + resource.fields['updated']._default = aware_datetime(2011, 9, 24, 0, 2) resp = resource.get_schema(request) self.assertEqual(resp.status_code, 200) @@ -2297,18 +2298,18 @@ def test_obj_update(self): note = NoteResource() note_obj = note.obj_get(pk=1) self.assertEqual(note_obj.title, u'Yet another another new post!') - self.assertEqual(note_obj.created, datetime.datetime(2010, 3, 30, 20, 5)) + self.assertEqual(note_obj.created, aware_datetime(2010, 3, 30, 20, 5)) note_bundle = note.build_bundle(obj=note_obj) note_bundle = note.full_dehydrate(note_bundle) note_bundle.data['title'] = 'OMGOMGOMGOMG!' - note_bundle.data['created'] = datetime.datetime(2011, 11, 23, 1, 0, 0) + note_bundle.data['created'] = aware_datetime(2011, 11, 23, 1, 0, 0) note.obj_update(note_bundle, pk=1, created='2010-03-30T20:05:00') self.assertEqual(Note.objects.all().count(), 6) numero_uno = Note.objects.get(pk=1) self.assertEqual(numero_uno.title, u'OMGOMGOMGOMG!') self.assertEqual(numero_uno.slug, u'yet-another-another-new-post') self.assertEqual(numero_uno.content, u'WHEEEEEE!') - self.assertEqual(numero_uno.created, datetime.datetime(2011, 11, 23, 1, 0)) + self.assertEqual(numero_uno.created, aware_datetime(2011, 11, 23, 1, 0)) # Now try a lookup that should fail. note = NoteResource() @@ -2695,7 +2696,7 @@ def test_readonly_full_hydrate(self): hbundle = Bundle(obj=note, data={ 'name': 'Daniel', 'view_count': 6, - 'date_joined': datetime.datetime(2010, 2, 15, 12, 0, 0), + 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0), }) hydrated = rornr.full_hydrate(hbundle) self.assertEqual(hydrated.obj.author.username, 'johndoe') @@ -2704,7 +2705,7 @@ def test_readonly_full_hydrate(self): hbundle_2 = Bundle(obj=note, data={ 'name': 'Daniel', 'view_count': 6, - 'date_joined': datetime.datetime(2010, 2, 15, 12, 0, 0), + 'date_joined': aware_datetime(2010, 2, 15, 12, 0, 0), 'author': '/api/v1/users/2/', }) hydrated_2 = rornr.full_hydrate(hbundle_2) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 000000000..1a50d1805 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,8 @@ +django-oauth-plus +oauth2 +lxml +python-digest +biplist +pyyaml +mimeparse>=0.1.3 +python-dateutil==1.5 diff --git a/tests/settings.py b/tests/settings.py index 93d399d29..11fcfa9b6 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -12,6 +12,16 @@ DATABASE_NAME = 'tastypie.db' TEST_DATABASE_NAME = 'tastypie-test.db' +# for forwards compatibility +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.%s' % DATABASE_ENGINE, + 'NAME': DATABASE_NAME, + 'TEST_NAME': TEST_DATABASE_NAME, + } +} + + INSTALLED_APPS = [ 'django.contrib.auth', 'django.contrib.contenttypes', @@ -21,3 +31,6 @@ DEBUG = True TEMPLATE_DEBUG = DEBUG CACHE_BACKEND = 'locmem://' + +# to make sure timezones are handled correctly in Django>=1.4 +USE_TZ = True diff --git a/tox.ini b/tox.ini index a1e558346..5bb7fd220 100644 --- a/tox.ini +++ b/tox.ini @@ -1,29 +1,40 @@ [tox] -envlist = py25-1.2.X,py26-1.2.X,py27-1.2.X,py25,py26,py27,docs -downloadcache = .tox/_download/ +envlist = py25-1.4.X,py26-1.4.X,py27-1.4.X,py25,py26,py27,docs [testenv] setenv = - PYTHONPATH = {toxinidir}/tests + PYTHONPATH = {toxinidir}:{toxinidir}/tests commands = - {toxinidir}/tests/run_all_tests.sh + {envbindir}/django-admin.py test core --settings=settings_core + {envbindir}/django-admin.py test basic --settings=settings_basic + #{envbindir}/django-admin.py test complex --settings=settings_complex + {envbindir}/django-admin.py test alphanumeric --settings=settings_alphanumeric + {envbindir}/django-admin.py test slashless --settings=settings_slashless + {envbindir}/django-admin.py test namespaced --settings=settings_namespaced + {envbindir}/django-admin.py test related_resource --settings=settings_related + {envbindir}/django-admin.py test validation --settings=settings_validation + deps = + -r{toxinidir}/tests/requirements.txt django==1.3 -[testenv:py25-1.2.X] +[testenv:py25-1.4.X] basepython = python2.5 deps = - http://www.djangoproject.com/download/1.2.5/tarball/ + -r{toxinidir}/tests/requirements.txt + https://github.com/django/django/zipball/master -[testenv:py26-1.2.X] +[testenv:py26-1.4.X] basepython = python2.6 deps = - http://www.djangoproject.com/download/1.2.5/tarball/ + -r{toxinidir}/tests/requirements.txt + https://github.com/django/django/zipball/master -[testenv:py27-1.2.X] +[testenv:py27-1.4.X] basepython = python2.7 deps = - http://www.djangoproject.com/download/1.2.5/tarball/ + -r{toxinidir}/tests/requirements.txt + https://github.com/django/django/zipball/master [testenv:py25] basepython = python2.5