Permalink
Browse files

Simple implementation of GET support.

  • Loading branch information...
1 parent 4126118 commit a5dd12c3c89b0394cdb7c61696d5daa16d9b2631 @af committed Nov 23, 2011
Showing with 157 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. 0 djangbone/__init__.py
  3. +1 −0 djangbone/models.py
  4. +67 −0 djangbone/tests.py
  5. +88 −0 djangbone/views.py
View
@@ -0,0 +1 @@
+*.pyc
View
No changes.
View
@@ -0,0 +1 @@
+# Empty models.py, which helps django recognize this as an app.
View
@@ -0,0 +1,67 @@
+import json
+from django.contrib.auth.models import User
+from django.http import Http404
+from django.test.client import RequestFactory
+from django.utils import unittest
+
+from djangbone.views import BackboneView
+
+
+class MyView(BackboneView):
+ """
+ The subclass used to test BackboneView.
+ """
+ model = User
+ serialize_fields = ('id', 'username', 'first_name', 'last_name')
+
+
+class ViewTest(unittest.TestCase):
+ """
+ Tests for BackboneView.
+
+ Note that django.contrib.auth must be in INSTALLED_APPS for these to work.
+ """
+ def setUp(self):
+ self.factory = RequestFactory()
+ self.view = MyView.as_view()
+ self.user1 = User.objects.create(username='test1', first_name='Test', last_name='One')
+
+ def tearDown(self):
+ User.objects.all().delete()
+
+ def add_two_more_users(self):
+ self.user2 = User.objects.create(username='test2', first_name='Test', last_name='Two')
+ self.user3 = User.objects.create(username='test3', first_name='Test', last_name='Three')
+
+ def test_collection_get(self):
+ request = self.factory.get('/users/')
+ response = self.view(request)
+ self.assertEqual(response.status_code, 200)
+ response_data = json.loads(response.content)
+
+ # Ensure response json deserializes to a 1-item list:
+ self.assert_(isinstance(response_data, list))
+ self.assertEqual(len(response_data), 1)
+ self.assertEqual(response_data[0]['username'], self.user1.username)
+
+ # Try again with a few more users in the database:
+ self.add_two_more_users()
+ response = self.view(request)
+ self.assertEqual(response.status_code, 200)
+ response_data = json.loads(response.content)
+ self.assert_(isinstance(response_data, list))
+ self.assertEqual(len(response_data), 3)
+ # With User model's default ordering (by id), user3 should be last:
+ self.assertEqual(response_data[2]['username'], self.user3.username)
+
+ def test_single_item_get(self):
+ request = self.factory.get('/users/1')
+ response = self.view(request, id='1') # Simulate a urlconf passing in the 'id' kwarg
+ self.assertEqual(response.status_code, 200)
+ response_data = json.loads(response.content)
+ self.assert_(isinstance(response_data, dict))
+ self.assertEqual(response_data['username'], self.user1.username)
+
+ # Ensure 404s are raised for non-existent items:
+ request = self.factory.get('/users/7')
+ self.assertRaises(Http404, lambda: self.view(request, id='7'))
View
@@ -0,0 +1,88 @@
+from datetime import datetime
+import json #FIXME: fallback to simplejson if json not available
+
+from django.http import HttpResponse, Http404
+from django.views.generic import View
+
+
+class BackboneView(View):
+ """
+ Abstract class view, which makes it easy for subclasses to talk to backbone.js.
+
+ Supported operations (copied from backbone.js docs):
+ create -> POST /collection
+ read -> GET /collection[/id]
+ update -> PUT /collection/id
+ delete -> DELETE /collection/id
+
+ Assumptions:
+ - Your model has an integer primary key named 'id'
+ - Your model has a Manager at Mymodel.objects
+ """
+ model = None
+ serialize_fields = tuple()
+
+ def get(self, request, *args, **kwargs):
+ """
+ Handle GET requests, either for a single resource or a collection.
+ """
+ if kwargs.get('id'):
+ return self.get_single_item(request, *args, **kwargs)
+ else:
+ return self.get_collection(request, *args, **kwargs)
+
+ def get_single_item(self, request, *args, **kwargs):
+ """
+ Handle a GET request for a single model instance.
+ """
+ try:
+ qs = self.model.objects.filter(id=kwargs['id'])
+ assert len(qs) == 1
+ except AssertionError:
+ raise Http404
+ output = self.serialize_qs(qs)
+ return self.build_response(output)
+
+ def get_collection(self, request, *args, **kwargs):
+ """
+ Handle a GET request for a full collection (when no id was provided).
+ """
+ qs = self.model.objects.all()
+ output = self.serialize_qs(qs)
+ return self.build_response(output)
+
+ def post(self, request, *args, **kwargs):
+ pass
+
+ def put(self, request, *args, **kwargs):
+ pass
+
+ def delete(self, request, *args, **kwargs):
+ pass
+
+ def serialize_qs(self, queryset):
+ """
+ Serialize a queryset into a JSON object that can be consumed by backbone.js.
+ """
+ values = queryset.values(*self.serialize_fields)
+ if self.kwargs.get('id'):
+ # For single-item requests, convert ValuesQueryset to a dict simply
+ # by slicing the first item:
+ json_output = json.dumps(values[0], default=BackboneView.date_serializer)
+ else:
+ json_output = json.dumps(list(values), default=BackboneView.date_serializer)
+ return json_output
+
+ def build_response(self, output):
+ """
+ Convert json to an HttpResponse object, with the correct mimetype
+ """
+ return HttpResponse(output, mimetype='application/json')
+
+
+ @staticmethod
+ def date_serializer(obj):
+ """
+ Convert datetime objects to ISO-compatible strings during json serialization.
+ """
+ return obj.isoformat() if isinstance(obj, datetime.datetime) else None

0 comments on commit a5dd12c

Please sign in to comment.