Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

WIP contenttypes/GFK integration.

Needs:
more thought about tests
code review
release notes
documentation
gracious thanking of the people who helped
Python 2.5 solution for context managers in tests.
  • Loading branch information...
commit 02988b04443fca9d3f6e00d7ea39d9def6897459 1 parent 9479190
@issackelly issackelly authored
View
0  tastypie/contrib/contenttypes/__init__.py
No changes.
View
43 tastypie/contrib/contenttypes/fields.py
@@ -0,0 +1,43 @@
+from tastypie import fields
+from tastypie.resources import Resource
+from tastypie.exceptions import ApiFieldError
+from django.db import models
+from django.core.exceptions import ObjectDoesNotExist
+from .resources import GenericResource
+
+
+class GenericForeignKeyField(fields.ToOneField):
+ def __init__(self, to, attribute, **kwargs):
+ if not isinstance(to, dict):
+ raise ValueError('to field must be a dictionary in GenericForeignKeyField')
+ if len(to) <= 0:
+ raise ValueError('to field must have some values')
+ for k, v in to.iteritems():
+ if not issubclass(k, models.Model) or not issubclass(v, Resource):
+ raise ValueError('to field must map django models to tastypie resources')
+ super(GenericForeignKeyField, self).__init__(to, attribute, **kwargs)
+
+ def get_related_resource(self, related_instance):
+ self._to_class = self.to.get(type(related_instance), None)
+ if self._to_class is None:
+ raise TypeError('no resource for model %s' % type(related_instance))
+ return super(GenericForeignKeyField, self).get_related_resource(related_instance)
+
+ @property
+ def to_class(self):
+ if self._to_class and not issubclass(GenericResource, self._to_class):
+ return self._to_class
+ return GenericResource
+
+ def resource_from_uri(self, fk_resource, uri, request=None, related_obj=None, related_name=None):
+ try:
+ obj = fk_resource.get_via_uri(uri, request=request)
+ fk_resource = self.get_related_resource(obj)
+ return super(GenericForeignKeyField, self).resource_from_uri(
+ fk_resource, uri, request, related_obj, related_name)
+ except ObjectDoesNotExist:
+ raise ApiFieldError("Could not find the provided object via resource URI '%s'." % uri)
+
+ def build_related_resource(self, *args, **kwargs):
+ self._to_class = None
+ return super(GenericForeignKeyField, self).build_related_resource(*args, **kwargs)
View
32 tastypie/contrib/contenttypes/resources.py
@@ -0,0 +1,32 @@
+from tastypie.resources import ModelResource
+from tastypie.exceptions import NotFound
+from django.core.urlresolvers import resolve, Resolver404, get_script_prefix
+
+
+class GenericResource(ModelResource):
+ def get_via_uri(self, uri, request=None):
+ """
+ This pulls apart the salient bits of the URI and populates the
+ resource via a ``obj_get``.
+
+ Optionally accepts a ``request``.
+
+ If you need custom behavior based on other portions of the URI,
+ simply override this method.
+ """
+ prefix = get_script_prefix()
+ chomped_uri = uri
+
+ if prefix and chomped_uri.startswith(prefix):
+ chomped_uri = chomped_uri[len(prefix)-1:]
+
+ try:
+ view, args, kwargs = resolve(chomped_uri)
+ except Resolver404:
+ raise NotFound("The URL provided '%s' was not a link to a valid resource." % uri)
+
+ # FIXME Hack to climb the prototype chain to get the parent resource
+ # for this given uri.
+ parent_resource = view.func_closure[0].cell_contents.func_closure[0].cell_contents.func_closure[0].cell_contents
+ return parent_resource.obj_get(**self.remove_api_resource_names(kwargs))
+
View
0  tests/content_gfk/__init__.py
No changes.
View
0  tests/content_gfk/api/__init__.py
No changes.
View
33 tests/content_gfk/api/resources.py
@@ -0,0 +1,33 @@
+from tastypie.contrib.contenttypes.fields import GenericForeignKeyField
+from tastypie.resources import ModelResource
+from content_gfk.models import Note, Quote, Definition, Rating
+
+
+class DefinitionResource(ModelResource):
+
+ class Meta:
+ resource_name = 'definitions'
+ queryset = Definition.objects.all()
+
+class NoteResource(ModelResource):
+
+ class Meta:
+ resource_name = 'notes'
+ queryset = Note.objects.all()
+
+
+class QuoteResource(ModelResource):
+
+ class Meta:
+ resource_name = 'quotes'
+ queryset = Quote.objects.all()
+
+class RatingResource(ModelResource):
+ content_object = GenericForeignKeyField({
+ Note: NoteResource,
+ Quote: QuoteResource
+ }, 'content_object')
+
+ class Meta:
+ resource_name = 'ratings'
+ queryset = Rating.objects.all()
View
13 tests/content_gfk/api/urls.py
@@ -0,0 +1,13 @@
+from django.conf.urls.defaults import *
+from tastypie.api import Api
+from content_gfk.api.resources import NoteResource, QuoteResource, \
+ RatingResource, DefinitionResource
+
+
+api = Api(api_name='v1')
+api.register(NoteResource())
+api.register(QuoteResource())
+api.register(RatingResource())
+api.register(DefinitionResource())
+
+urlpatterns = api.urls
View
26 tests/content_gfk/models.py
@@ -0,0 +1,26 @@
+from django.db import models
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
+
+
+class Definition(models.Model):
+ word = models.CharField(max_length=255)
+ content = models.TextField()
+
+class Note(models.Model):
+ title = models.CharField(max_length=255)
+ content = models.TextField()
+
+
+class Quote(models.Model):
+ byline = models.CharField(max_length=255)
+ content = models.TextField()
+
+
+class Rating(models.Model):
+ RATINGS = [ (x, x) for x in range(1, 6) ]
+
+ rating = models.PositiveIntegerField(choices=RATINGS, default=3)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
View
5 tests/content_gfk/tests/__init__.py
@@ -0,0 +1,5 @@
+import warnings
+warnings.simplefilter('ignore', Warning)
+
+#from content_gfk.tests.resources import *
+from content_gfk.tests.fields import *
View
82 tests/content_gfk/tests/fields.py
@@ -0,0 +1,82 @@
+from django.test import TestCase
+from tastypie.contrib.contenttypes.fields import GenericForeignKeyField
+from tastypie.bundle import Bundle
+from content_gfk.models import Note, Quote, Rating, Definition
+from content_gfk.api.resources import NoteResource, DefinitionResource, \
+ QuoteResource, RatingResource
+
+
+class ContentTypeFieldTestCase(TestCase):
+
+ def test_init(self):
+ with self.assertRaises(ValueError):
+ # Test that you have to use a dict some other resources
+ GenericForeignKeyField(((Note, NoteResource)), 'nofield')
+
+ # Test that you must register some other resources
+ GenericForeignKeyField({}, 'nofield')
+
+ # Test that the resources you raise must be models
+ GenericForeignKeyField({'str': 'ing'}, 'nofield')
+
+
+ def test_get_related_resource(self):
+ gfk_field = GenericForeignKeyField({
+ Note: NoteResource,
+ Quote: QuoteResource
+ }, 'nofield')
+
+ definition_1 = Definition.objects.create(
+ word='toast',
+ content="Cook or brown (food, esp. bread or cheese)"
+ )
+
+ # Test that you can not link to a model that does not have a resource
+ with self.assertRaises(TypeError):
+ gfk_field.get_related_resource(definition_1)
+
+ note_1 = Note.objects.create(
+ title='All aboard the rest train',
+ content='Sometimes it is just better to lorem ipsum'
+ )
+
+ self.assertTrue(isinstance(gfk_field.get_related_resource(note_1), NoteResource))
+
+ def test_resource_from_uri(self):
+ note_2 = Note.objects.create(
+ title='Generic and such',
+ content='Sometimes it is to lorem ipsum'
+ )
+
+ gfk_field = GenericForeignKeyField({
+ Note: NoteResource,
+ Quote: QuoteResource
+ }, 'nofield')
+
+ self.assertEqual(
+ gfk_field.resource_from_uri(
+ gfk_field.to_class(),
+ '/api/v1/notes/%s/' % note_2.pk
+ ).obj,
+ note_2
+ )
+
+ def test_build_related_resource(self):
+ gfk_field = GenericForeignKeyField({
+ Note: NoteResource,
+ Quote: QuoteResource
+ }, 'nofield')
+
+ quote_1 = Quote.objects.create(
+ byline='Issac Kelly',
+ content='To ipsum or not to ipsum, that is the cliche'
+ )
+ qr = QuoteResource()
+ qr.build_bundle(obj=quote_1)
+
+ bundle = gfk_field.build_related_resource(
+ '/api/v1/quotes/%s/' % quote_1.pk
+ )
+
+ # Test that the GFK field builds the same as the QuoteResource
+ self.assertEqual(bundle.obj, quote_1)
View
0  tests/content_gfk/tests/resources.py
No changes.
View
5 tests/content_gfk/urls.py
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ (r'^api/', include('content_gfk.api.urls')),
+)
View
18 tests/manage_contenttypes.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+import os
+import sys
+
+from os.path import abspath, dirname, join
+from django.core.management import execute_manager
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+try:
+ import settings_contenttypes as settings
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings_core.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
+
View
2  tests/run_all_tests.sh
@@ -4,7 +4,7 @@ PYTHONPATH=$PWD:$PWD/..${PYTHONPATH:+:$PYTHONPATH}
export PYTHONPATH
# complex
-ALL="core basic alphanumeric slashless namespaced related validation gis"
+ALL="core basic alphanumeric slashless namespaced related validation gis content_gfk"
if [ $# -eq 0 ]; then
TYPES=$ALL
View
7 tests/settings_content_gfk.py
@@ -0,0 +1,7 @@
+from settings import *
+
+INSTALLED_APPS += [
+ 'content_gfk',
+]
+
+ROOT_URLCONF = 'content_gfk.urls'
Please sign in to comment.
Something went wrong with that request. Please try again.