aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authortom christie tom@tomchristie.com2011-01-23 23:08:16 +0000
committertom christie tom@tomchristie.com2011-01-23 23:08:16 +0000
commit4100242fa2395bef8db0c5ffbab6f5d0cf95301d (patch)
tree0c8efd4dfdbdc6af7dca234291f8f8c7e3b035ff /examples
parent99799032721a32220c32d4a74a950bdd07b13cb3 (diff)
downloaddjango-rest-framework-4100242fa2395bef8db0c5ffbab6f5d0cf95301d.tar.bz2
Sphinx docs, examples, lots of refactoring
Diffstat (limited to 'examples')
-rw-r--r--examples/__init__.py0
-rw-r--r--examples/blogpost/__init__.py0
-rw-r--r--examples/blogpost/models.py68
-rw-r--r--examples/blogpost/tests.py163
-rw-r--r--examples/blogpost/urls.py11
-rw-r--r--examples/blogpost/views.py63
-rw-r--r--examples/initial_data.json20
-rwxr-xr-xexamples/manage.py11
-rw-r--r--examples/objectstore/__init__.py0
-rw-r--r--examples/objectstore/models.py3
-rw-r--r--examples/objectstore/tests.py23
-rw-r--r--examples/objectstore/urls.py6
-rw-r--r--examples/objectstore/views.py54
-rw-r--r--examples/settings.py96
-rw-r--r--examples/urls.py11
15 files changed, 529 insertions, 0 deletions
diff --git a/examples/__init__.py b/examples/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/examples/__init__.py
diff --git a/examples/blogpost/__init__.py b/examples/blogpost/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/examples/blogpost/__init__.py
diff --git a/examples/blogpost/models.py b/examples/blogpost/models.py
new file mode 100644
index 00000000..1690245c
--- /dev/null
+++ b/examples/blogpost/models.py
@@ -0,0 +1,68 @@
+from django.db import models
+from django.template.defaultfilters import slugify
+import uuid
+
+def uuid_str():
+ return str(uuid.uuid1())
+
+
+RATING_CHOICES = ((0, 'Awful'),
+ (1, 'Poor'),
+ (2, 'OK'),
+ (3, 'Good'),
+ (4, 'Excellent'))
+
+class BlogPost(models.Model):
+ key = models.CharField(primary_key=True, max_length=64, default=uuid_str, editable=False)
+ title = models.CharField(max_length=128)
+ content = models.TextField()
+ created = models.DateTimeField(auto_now_add=True)
+ slug = models.SlugField(editable=False, default='')
+
+ class Meta:
+ ordering = ('created',)
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ('blogpost.views.BlogPostInstance', (), {'key': self.key})
+
+ @property
+ @models.permalink
+ def comments_url(self):
+ """Link to a resource which lists all comments for this blog post."""
+ return ('blogpost.views.CommentList', (), {'blogpost_id': self.key})
+
+ @property
+ @models.permalink
+ def comment_url(self):
+ """Link to a resource which can create a comment for this blog post."""
+ return ('blogpost.views.CommentCreator', (), {'blogpost_id': self.key})
+
+ def __unicode__(self):
+ return self.title
+
+ def save(self, *args, **kwargs):
+ self.slug = slugify(self.title)
+ super(self.__class__, self).save(*args, **kwargs)
+
+
+class Comment(models.Model):
+ blogpost = models.ForeignKey(BlogPost, editable=False, related_name='comments')
+ username = models.CharField(max_length=128)
+ comment = models.TextField()
+ rating = models.IntegerField(blank=True, null=True, choices=RATING_CHOICES, help_text='How did you rate this post?')
+ created = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ ordering = ('created',)
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ('blogpost.views.CommentInstance', (), {'blogpost': self.blogpost.key, 'id': self.id})
+
+ @property
+ @models.permalink
+ def blogpost_url(self):
+ """Link to the blog post resource which this comment corresponds to."""
+ return ('blogpost.views.BlogPostInstance', (), {'key': self.blogpost.key})
+
diff --git a/examples/blogpost/tests.py b/examples/blogpost/tests.py
new file mode 100644
index 00000000..43789399
--- /dev/null
+++ b/examples/blogpost/tests.py
@@ -0,0 +1,163 @@
+"""Test a range of REST API usage of the example application.
+"""
+
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+from blogpost import views
+#import json
+#from rest.utils import xml2dict, dict2xml
+
+
+class AcceptHeaderTests(TestCase):
+ """Test correct behaviour of the Accept header as specified by RFC 2616:
+
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1"""
+
+ def assert_accept_mimetype(self, mimetype, expect=None):
+ """Assert that a request with given mimetype in the accept header,
+ gives a response with the appropriate content-type."""
+ if expect is None:
+ expect = mimetype
+
+ resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT=mimetype)
+
+ self.assertEquals(resp['content-type'], expect)
+
+
+ def test_accept_json(self):
+ """Ensure server responds with Content-Type of JSON when requested."""
+ self.assert_accept_mimetype('application/json')
+
+ def test_accept_xml(self):
+ """Ensure server responds with Content-Type of XML when requested."""
+ self.assert_accept_mimetype('application/xml')
+
+ def test_accept_json_when_prefered_to_xml(self):
+ """Ensure server responds with Content-Type of JSON when it is the client's prefered choice."""
+ self.assert_accept_mimetype('application/json;q=0.9, application/xml;q=0.1', expect='application/json')
+
+ def test_accept_xml_when_prefered_to_json(self):
+ """Ensure server responds with Content-Type of XML when it is the client's prefered choice."""
+ self.assert_accept_mimetype('application/json;q=0.1, application/xml;q=0.9', expect='application/xml')
+
+ def test_default_json_prefered(self):
+ """Ensure server responds with JSON in preference to XML."""
+ self.assert_accept_mimetype('application/json,application/xml', expect='application/json')
+
+ def test_accept_generic_subtype_format(self):
+ """Ensure server responds with an appropriate type, when the subtype is left generic."""
+ self.assert_accept_mimetype('text/*', expect='text/html')
+
+ def test_accept_generic_type_format(self):
+ """Ensure server responds with an appropriate type, when the type and subtype are left generic."""
+ self.assert_accept_mimetype('*/*', expect='application/json')
+
+ def test_invalid_accept_header_returns_406(self):
+ """Ensure server returns a 406 (not acceptable) response if we set the Accept header to junk."""
+ resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT='invalid/invalid')
+ self.assertNotEquals(resp['content-type'], 'invalid/invalid')
+ self.assertEquals(resp.status_code, 406)
+
+ def test_prefer_specific_over_generic(self): # This test is broken right now
+ """More specific accept types have precedence over less specific types."""
+ self.assert_accept_mimetype('application/xml, */*', expect='application/xml')
+ self.assert_accept_mimetype('*/*, application/xml', expect='application/xml')
+
+
+class AllowedMethodsTests(TestCase):
+ """Basic tests to check that only allowed operations may be performed on a Resource"""
+
+ def test_reading_a_read_only_resource_is_allowed(self):
+ """GET requests on a read only resource should default to a 200 (OK) response"""
+ resp = self.client.get(reverse(views.RootResource))
+ self.assertEquals(resp.status_code, 200)
+
+ def test_writing_to_read_only_resource_is_not_allowed(self):
+ """PUT requests on a read only resource should default to a 405 (method not allowed) response"""
+ resp = self.client.put(reverse(views.RootResource), {})
+ self.assertEquals(resp.status_code, 405)
+#
+# def test_reading_write_only_not_allowed(self):
+# resp = self.client.get(reverse(views.WriteOnlyResource))
+# self.assertEquals(resp.status_code, 405)
+#
+# def test_writing_write_only_allowed(self):
+# resp = self.client.put(reverse(views.WriteOnlyResource), {})
+# self.assertEquals(resp.status_code, 200)
+#
+#
+#class EncodeDecodeTests(TestCase):
+# def setUp(self):
+# super(self.__class__, self).setUp()
+# self.input = {'a': 1, 'b': 'example'}
+#
+# def test_encode_form_decode_json(self):
+# content = self.input
+# resp = self.client.put(reverse(views.WriteOnlyResource), content)
+# output = json.loads(resp.content)
+# self.assertEquals(self.input, output)
+#
+# def test_encode_json_decode_json(self):
+# content = json.dumps(self.input)
+# resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json')
+# output = json.loads(resp.content)
+# self.assertEquals(self.input, output)
+#
+# #def test_encode_xml_decode_json(self):
+# # content = dict2xml(self.input)
+# # resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/json')
+# # output = json.loads(resp.content)
+# # self.assertEquals(self.input, output)
+#
+# #def test_encode_form_decode_xml(self):
+# # content = self.input
+# # resp = self.client.put(reverse(views.WriteOnlyResource), content, HTTP_ACCEPT='application/xml')
+# # output = xml2dict(resp.content)
+# # self.assertEquals(self.input, output)
+#
+# #def test_encode_json_decode_xml(self):
+# # content = json.dumps(self.input)
+# # resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/xml')
+# # output = xml2dict(resp.content)
+# # self.assertEquals(self.input, output)
+#
+# #def test_encode_xml_decode_xml(self):
+# # content = dict2xml(self.input)
+# # resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/xml')
+# # output = xml2dict(resp.content)
+# # self.assertEquals(self.input, output)
+#
+#class ModelTests(TestCase):
+# def test_create_container(self):
+# content = json.dumps({'name': 'example'})
+# resp = self.client.post(reverse(views.ContainerFactory), content, 'application/json')
+# output = json.loads(resp.content)
+# self.assertEquals(resp.status_code, 201)
+# self.assertEquals(output['name'], 'example')
+# self.assertEquals(set(output.keys()), set(('absolute_uri', 'name', 'key')))
+#
+#class CreatedModelTests(TestCase):
+# def setUp(self):
+# content = json.dumps({'name': 'example'})
+# resp = self.client.post(reverse(views.ContainerFactory), content, 'application/json', HTTP_ACCEPT='application/json')
+# self.container = json.loads(resp.content)
+#
+# def test_read_container(self):
+# resp = self.client.get(self.container["absolute_uri"])
+# self.assertEquals(resp.status_code, 200)
+# container = json.loads(resp.content)
+# self.assertEquals(container, self.container)
+#
+# def test_delete_container(self):
+# resp = self.client.delete(self.container["absolute_uri"])
+# self.assertEquals(resp.status_code, 204)
+# self.assertEquals(resp.content, '')
+#
+# def test_update_container(self):
+# self.container['name'] = 'new'
+# content = json.dumps(self.container)
+# resp = self.client.put(self.container["absolute_uri"], content, 'application/json')
+# self.assertEquals(resp.status_code, 200)
+# container = json.loads(resp.content)
+# self.assertEquals(container, self.container)
+
diff --git a/examples/blogpost/urls.py b/examples/blogpost/urls.py
new file mode 100644
index 00000000..eccbae15
--- /dev/null
+++ b/examples/blogpost/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import patterns
+
+urlpatterns = patterns('blogpost.views',
+ (r'^$', 'RootResource'),
+ (r'^blog-posts/$', 'BlogPostList'),
+ (r'^blog-post/$', 'BlogPostCreator'),
+ (r'^blog-post/(?P<key>[^/]+)/$', 'BlogPostInstance'),
+ (r'^blog-post/(?P<blogpost_id>[^/]+)/comments/$', 'CommentList'),
+ (r'^blog-post/(?P<blogpost_id>[^/]+)/comment/$', 'CommentCreator'),
+ (r'^blog-post/(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', 'CommentInstance'),
+)
diff --git a/examples/blogpost/views.py b/examples/blogpost/views.py
new file mode 100644
index 00000000..05e795fa
--- /dev/null
+++ b/examples/blogpost/views.py
@@ -0,0 +1,63 @@
+from flywheel.response import Response, status
+from flywheel.resource import Resource
+from flywheel.modelresource import ModelResource, QueryModelResource
+from blogpost.models import BlogPost, Comment
+
+##### Root Resource #####
+
+class RootResource(Resource):
+ """This is the top level resource for the API.
+ All the sub-resources are discoverable from here."""
+ allowed_methods = ('GET',)
+
+ def get(self, request, *args, **kwargs):
+ return Response(status.HTTP_200_OK,
+ {'blog-posts': self.reverse(BlogPostList),
+ 'blog-post': self.reverse(BlogPostCreator)})
+
+
+##### Blog Post Resources #####
+
+BLOG_POST_FIELDS = ('created', 'title', 'slug', 'content', 'absolute_url', 'comment_url', 'comments_url')
+
+class BlogPostList(QueryModelResource):
+ """A resource which lists all existing blog posts."""
+ allowed_methods = ('GET', )
+ model = BlogPost
+ fields = BLOG_POST_FIELDS
+
+class BlogPostCreator(ModelResource):
+ """A resource with which blog posts may be created."""
+ allowed_methods = ('POST',)
+ model = BlogPost
+ fields = BLOG_POST_FIELDS
+
+class BlogPostInstance(ModelResource):
+ """A resource which represents a single blog post."""
+ allowed_methods = ('GET', 'PUT', 'DELETE')
+ model = BlogPost
+ fields = BLOG_POST_FIELDS
+
+
+##### Comment Resources #####
+
+COMMENT_FIELDS = ('username', 'comment', 'created', 'rating', 'absolute_url', 'blogpost_url')
+
+class CommentList(QueryModelResource):
+ """A resource which lists all existing comments for a given blog post."""
+ allowed_methods = ('GET', )
+ model = Comment
+ fields = COMMENT_FIELDS
+
+class CommentCreator(ModelResource):
+ """A resource with which blog comments may be created for a given blog post."""
+ allowed_methods = ('POST',)
+ model = Comment
+ fields = COMMENT_FIELDS
+
+class CommentInstance(ModelResource):
+ """A resource which represents a single comment."""
+ allowed_methods = ('GET', 'PUT', 'DELETE')
+ model = Comment
+ fields = COMMENT_FIELDS
+
diff --git a/examples/initial_data.json b/examples/initial_data.json
new file mode 100644
index 00000000..62103cf9
--- /dev/null
+++ b/examples/initial_data.json
@@ -0,0 +1,20 @@
+[
+ {
+ "pk": 1,
+ "model": "auth.user",
+ "fields": {
+ "username": "admin",
+ "first_name": "",
+ "last_name": "",
+ "is_active": true,
+ "is_superuser": true,
+ "is_staff": true,
+ "last_login": "2010-01-01 00:00:00",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$6cbce$e4e808893d586a3301ac3c14da6c84855999f1d8",
+ "email": "test@example.com",
+ "date_joined": "2010-01-01 00:00:00"
+ }
+ }
+] \ No newline at end of file
diff --git a/examples/manage.py b/examples/manage.py
new file mode 100755
index 00000000..5e78ea97
--- /dev/null
+++ b/examples/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.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)
diff --git a/examples/objectstore/__init__.py b/examples/objectstore/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/examples/objectstore/__init__.py
diff --git a/examples/objectstore/models.py b/examples/objectstore/models.py
new file mode 100644
index 00000000..71a83623
--- /dev/null
+++ b/examples/objectstore/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/examples/objectstore/tests.py b/examples/objectstore/tests.py
new file mode 100644
index 00000000..2247054b
--- /dev/null
+++ b/examples/objectstore/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/examples/objectstore/urls.py b/examples/objectstore/urls.py
new file mode 100644
index 00000000..c04e731e
--- /dev/null
+++ b/examples/objectstore/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import patterns
+
+urlpatterns = patterns('objectstore.views',
+ (r'^$', 'ObjectStoreRoot'),
+ (r'^(?P<key>[A-Za-z0-9_-]{1,64})/$', 'StoredObject'),
+)
diff --git a/examples/objectstore/views.py b/examples/objectstore/views.py
new file mode 100644
index 00000000..16c7f8e9
--- /dev/null
+++ b/examples/objectstore/views.py
@@ -0,0 +1,54 @@
+from django.conf import settings
+
+from flywheel.resource import Resource
+from flywheel.response import Response, status
+
+import pickle
+import os
+import uuid
+
+OBJECT_STORE_DIR = os.path.join(settings.MEDIA_ROOT, 'objectstore')
+
+
+class ObjectStoreRoot(Resource):
+ """Root of the Object Store API.
+ Allows the client to get a complete list of all the stored objects, or to create a new stored object."""
+ allowed_methods = ('GET', 'POST')
+
+ def get(self, request):
+ """Return a list of all the stored object URLs."""
+ keys = sorted(os.listdir(OBJECT_STORE_DIR))
+ return [self.reverse(StoredObject, key=key) for key in keys]
+
+ def post(self, request, content):
+ """Create a new stored object, with a unique key."""
+ key = str(uuid.uuid1())
+ pathname = os.path.join(OBJECT_STORE_DIR, key)
+ pickle.dump(content, open(pathname, 'wb'))
+ return Response(status.HTTP_201_CREATED, content, {'Location': self.reverse(StoredObject, key=key)})
+
+
+class StoredObject(Resource):
+ """Represents a stored object.
+ The object may be any picklable content."""
+ allowed_methods = ('GET', 'PUT', 'DELETE')
+
+ def get(self, request, key):
+ """Return a stored object, by unpickling the contents of a locally stored file."""
+ pathname = os.path.join(OBJECT_STORE_DIR, key)
+ if not os.path.exists(pathname):
+ return Response(status.HTTP_404_NOT_FOUND)
+ return pickle.load(open(pathname, 'rb'))
+
+ def put(self, request, content, key):
+ """Update/create a stored object, by pickling the request content to a locally stored file."""
+ pathname = os.path.join(OBJECT_STORE_DIR, key)
+ pickle.dump(content, open(pathname, 'wb'))
+ return content
+
+ def delete(self, request, key):
+ """Delete a stored object, by removing it's pickled file."""
+ pathname = os.path.join(OBJECT_STORE_DIR, key)
+ if not os.path.exists(pathname):
+ return Response(status.HTTP_404_NOT_FOUND)
+ os.remove(pathname)
diff --git a/examples/settings.py b/examples/settings.py
new file mode 100644
index 00000000..d1635104
--- /dev/null
+++ b/examples/settings.py
@@ -0,0 +1,96 @@
+# Django settings for src project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': 'sqlite3.db', # Or path to database file if using sqlite3.
+ 'USER': '', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ }
+}
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'Europe/London'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-uk'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = '/Users/tomchristie/'
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.admin',
+ 'flywheel',
+ 'blogpost',
+ 'objectstore'
+)
diff --git a/examples/urls.py b/examples/urls.py
new file mode 100644
index 00000000..03791084
--- /dev/null
+++ b/examples/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import patterns, include
+from django.contrib import admin
+
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ (r'^blog-post-example/', include('blogpost.urls')),
+ (r'^object-store-example/', include('objectstore.urls')),
+ (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+ (r'^admin/', include(admin.site.urls)),
+)