mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-23 22:49:50 +03:00
Merge 3f81cde883
into d4f8b4cf06
This commit is contained in:
commit
51860dddb0
40
djangorestframework/resources.py
Normal file
40
djangorestframework/resources.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from djangorestframework.serializers import ModelSerializer
|
||||
from djangorestframework.generics import RootAPIView, InstanceAPIView
|
||||
|
||||
class ModelResource (object):
|
||||
serializer_class = ModelSerializer
|
||||
collection_view_class = RootAPIView
|
||||
instance_view_class = InstanceAPIView
|
||||
|
||||
# The collection_name is the path at the root of the resource. For
|
||||
# example, say we have a Dog model, and a dog with id=1:
|
||||
#
|
||||
# http://api.example.com/dogs/1/
|
||||
#
|
||||
# The collection name is 'dogs'. This will default to the plural name
|
||||
# for the model.
|
||||
#
|
||||
collection_name = None
|
||||
|
||||
# The instance_name is the name of one model instance, and is used as a
|
||||
# prefix for internal URL names. For example, for out Dog model with
|
||||
# instance_name 'dog', may have the following urls:
|
||||
#
|
||||
# url('dogs/', collection_view, name='dog_collection'),
|
||||
# url('dogs/(P<pk>[^/])/)', instance_view, name='dog_instance'),
|
||||
#
|
||||
instance_name = None
|
||||
|
||||
# The id_field_name is the name of the field that will identify a
|
||||
# resource in the collection. For example, if we wanted our dogs
|
||||
# identified by a 'slug' field, we would have:
|
||||
#
|
||||
# url('dogs/(P<slug>[^/])/)', instance_view, name='dog_instance'),
|
||||
#
|
||||
# and:
|
||||
#
|
||||
# http://api.example.com/dogs/fido/
|
||||
#
|
||||
# The default value is 'pk'.
|
||||
#
|
||||
id_field_name = 'pk'
|
92
djangorestframework/routers.py
Normal file
92
djangorestframework/routers.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
# Note, these live in django.conf.urls since 1.4, and will no longer be
|
||||
# available from django.conf.urls.defaults in 1.6.
|
||||
|
||||
from djangorestframework.resources import ModelResource
|
||||
|
||||
class DefaultResourceRouter (object):
|
||||
|
||||
def __init__(self, default_resource=ModelResource):
|
||||
self.default_resource = default_resource
|
||||
self._registry = []
|
||||
|
||||
@property
|
||||
def urls(self):
|
||||
"""
|
||||
Return a urlpatterns object suitable for including. I.e.:
|
||||
|
||||
urlpatterns = patterns('',
|
||||
...
|
||||
url('^api/', include(router.urls, namespace=...)),
|
||||
...
|
||||
)
|
||||
"""
|
||||
return patterns('', *self.get_urls())
|
||||
|
||||
def get_urls(self):
|
||||
"""
|
||||
Return a list of urls for all registered resources.
|
||||
"""
|
||||
urls = []
|
||||
|
||||
for model, resource in self._registry:
|
||||
urls += self.make_patterns(
|
||||
model, resource, resource.id_field_name,
|
||||
resource.collection_name, resource.instance_name
|
||||
)
|
||||
|
||||
return urls
|
||||
|
||||
def make_patterns(self, model, resource, id_field_name=None,
|
||||
collection_name=None, instance_name=None):
|
||||
"""
|
||||
Get the URL patterns for the given model and resource. By default,
|
||||
this will return pair of urls -- one for the collection of resources
|
||||
representing the model, and one for individual instances of the model.
|
||||
"""
|
||||
patterns = []
|
||||
|
||||
if collection_name is None:
|
||||
collection_name = unicode(model._meta.verbose_name_plural)
|
||||
|
||||
if instance_name is None:
|
||||
instance_name = unicode(model._meta.verbose_name)
|
||||
|
||||
if id_field_name is None:
|
||||
id_field_name = u'pk'
|
||||
|
||||
# The collection
|
||||
if resource.collection_view_class:
|
||||
class CollectionView (resource, resource.collection_view_class):
|
||||
pass
|
||||
|
||||
collection_view = CollectionView.as_view()
|
||||
url_string = '^{0}/$'.format(collection_name)
|
||||
|
||||
patterns.append(
|
||||
url(url_string, collection_view,
|
||||
name='{0}_collection'.format(instance_name))
|
||||
)
|
||||
|
||||
# The instance
|
||||
if resource.instance_view_class:
|
||||
class InstanceView (resource, resource.instance_view_class):
|
||||
pass
|
||||
|
||||
instance_view = InstanceView.as_view()
|
||||
url_string = '^{0}/(?P<{1}>[^/]+)/$'.format(collection_name, id_field_name)
|
||||
|
||||
patterns.append(
|
||||
url(url_string, instance_view,
|
||||
name='{0}_instance'.format(instance_name))
|
||||
)
|
||||
|
||||
return patterns
|
||||
|
||||
def register(self, model, resource=None):
|
||||
"""
|
||||
Register a new resource with the API. By default a generic
|
||||
ModelResource will be used for the given model.
|
||||
"""
|
||||
resource = resource or self.default_resource
|
||||
self._registry.append((model, resource))
|
|
@ -91,6 +91,9 @@ INSTALLED_APPS = (
|
|||
# 'django.contrib.admindocs',
|
||||
'djangorestframework',
|
||||
'djangorestframework.tokenauth',
|
||||
|
||||
# Load up the models
|
||||
'djangorestframework.tests'
|
||||
)
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
|
|
|
@ -19,10 +19,18 @@ class CustomUser(models.Model):
|
|||
|
||||
class UserGroupMap(models.Model):
|
||||
user = models.ForeignKey(to=CustomUser)
|
||||
group = models.ForeignKey(to=Group)
|
||||
|
||||
group = models.ForeignKey(to=Group)
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self):
|
||||
return ('user_group_map', (), {
|
||||
'pk': self.id
|
||||
})
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
name = models.CharField(max_length=20)
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
employee_id = models.CharField(max_length=20, primary_key=True)
|
||||
|
|
140
djangorestframework/tests/routers.py
Normal file
140
djangorestframework/tests/routers.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
from django.test.testcases import TestCase
|
||||
from djangorestframework.generics import RootAPIView, InstanceAPIView, ListAPIView, DetailAPIView
|
||||
from djangorestframework.resources import ModelResource
|
||||
from djangorestframework.routers import DefaultResourceRouter
|
||||
from djangorestframework.serializers import Serializer, ModelSerializer
|
||||
from djangorestframework.tests.models import Company, Employee
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
import random
|
||||
import string
|
||||
|
||||
__all__ = ('DefaultResourceRouterTestCase',)
|
||||
|
||||
class DummyUrlConfModule(object):
|
||||
|
||||
def __init__(self, object_with_urls):
|
||||
self._object_with_urls = object_with_urls
|
||||
|
||||
@property
|
||||
def urlpatterns(self):
|
||||
urlpatterns = patterns('',
|
||||
url(r'^', include(self._object_with_urls.urls, namespace='api')),
|
||||
)
|
||||
return urlpatterns
|
||||
|
||||
|
||||
class DefaultResourceRouterTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.api = DefaultResourceRouter()
|
||||
self.urlconfmodule = DummyUrlConfModule(self.api)
|
||||
|
||||
def test_list_view(self):
|
||||
# Check that the URL gets registered
|
||||
self.api.register(Company)
|
||||
list_url = reverse('api:company_collection', urlconf=self.urlconfmodule)
|
||||
self.assertEqual(list_url, '/companys/')
|
||||
|
||||
def test_instance_view(self):
|
||||
self.api.register(Company)
|
||||
company = Company(name='Acme Ltd')
|
||||
company.save()
|
||||
|
||||
# Check that the URL gets registered
|
||||
instance_url = reverse(
|
||||
'api:company_instance', urlconf=self.urlconfmodule,
|
||||
kwargs={'pk':company.id},
|
||||
)
|
||||
self.assertEqual(instance_url, '/companys/' + str(company.id) + '/')
|
||||
|
||||
def test_instance_view_with_nonumeric_primary_key(self):
|
||||
"""
|
||||
Check that the api can properly reverse urls for models with
|
||||
non-numeric primary keys
|
||||
"""
|
||||
self.api.register(Employee)
|
||||
employee = Employee(employee_id='EMP001')
|
||||
employee.save()
|
||||
|
||||
instance_url = reverse(
|
||||
'api:employee_instance', urlconf=self.urlconfmodule,
|
||||
kwargs={'pk':employee.employee_id}
|
||||
)
|
||||
self.assertEqual(instance_url, '/employees/EMP001/')
|
||||
|
||||
def test_with_different_name(self):
|
||||
class CompanyResource (ModelResource):
|
||||
id_field_name = 'name'
|
||||
|
||||
self.api.register(Company, CompanyResource)
|
||||
company = Company(name='Acme')
|
||||
company.save()
|
||||
|
||||
instance_url = reverse(
|
||||
'api:company_instance', urlconf=self.urlconfmodule,
|
||||
kwargs={'name':company.name},
|
||||
)
|
||||
self.assertEqual(instance_url, '/companys/Acme/')
|
||||
|
||||
def test_with_different_collection_name(self):
|
||||
class CompanyResource (ModelResource):
|
||||
collection_name = 'companies'
|
||||
|
||||
self.api.register(Company, CompanyResource)
|
||||
|
||||
list_url = reverse('api:company_collection', urlconf=self.urlconfmodule)
|
||||
self.assertEqual(list_url, '/companies/')
|
||||
|
||||
instance_url = reverse('api:company_instance', urlconf=self.urlconfmodule, kwargs={'pk':1})
|
||||
self.assertEqual(instance_url, '/companies/1/')
|
||||
|
||||
def test_with_default_collection_view_class(self):
|
||||
self.api.register(Company)
|
||||
company = Company(name='Acme Ltd')
|
||||
company.save()
|
||||
|
||||
view = self.api.urls[0]._callback
|
||||
self.assertIsInstance(view.cls_instance, RootAPIView)
|
||||
self.assertIsInstance(view.cls_instance, ModelResource)
|
||||
|
||||
def test_with_default_instance_view_class(self):
|
||||
self.api.register(Company)
|
||||
|
||||
view = self.api.urls[1]._callback
|
||||
self.assertIsInstance(view.cls_instance, InstanceAPIView)
|
||||
self.assertIsInstance(view.cls_instance, ModelResource)
|
||||
|
||||
def test_with_different_collection_view_class(self):
|
||||
class CompanyResource(ModelResource):
|
||||
collection_view_class = ListAPIView
|
||||
self.api.register(Company, CompanyResource)
|
||||
|
||||
view = self.api.urls[0]._callback
|
||||
self.assertIsInstance(view.cls_instance, ListAPIView)
|
||||
self.assertIsInstance(view.cls_instance, ModelResource)
|
||||
|
||||
def test_with_different_instance_view_class(self):
|
||||
class CompanyResource(ModelResource):
|
||||
instance_view_class = DetailAPIView
|
||||
self.api.register(Company, CompanyResource)
|
||||
|
||||
view = self.api.urls[1]._callback
|
||||
self.assertIsInstance(view.cls_instance, DetailAPIView)
|
||||
self.assertIsInstance(view.cls_instance, ModelResource)
|
||||
|
||||
def test_with_default_serializer_class(self):
|
||||
self.api.register(Company)
|
||||
|
||||
view = self.api.urls[0]._callback
|
||||
self.assertIs(view.cls_instance.serializer_class, ModelSerializer)
|
||||
|
||||
def test_with_different_serializer_class(self):
|
||||
class CompanySerializer(Serializer):
|
||||
pass
|
||||
class CompanyResource(ModelResource):
|
||||
serializer_class = CompanySerializer
|
||||
self.api.register(Company, CompanyResource)
|
||||
|
||||
view = self.api.urls[0]._callback
|
||||
self.assertIs(view.cls_instance.serializer_class, CompanySerializer)
|
|
@ -1,36 +1,72 @@
|
|||
serializers.py
|
||||
|
||||
class BlogPostSerializer(URLModelSerializer):
|
||||
class Meta:
|
||||
model = BlogPost
|
||||
|
||||
class CommentSerializer(URLModelSerializer):
|
||||
class Meta:
|
||||
model = Comment
|
||||
## Registering Resources
|
||||
|
||||
resources.py
|
||||
|
||||
class BlogPostResource(ModelResource):
|
||||
serializer_class = BlogPostSerializer
|
||||
model = BlogPost
|
||||
permissions = [AdminOrAnonReadonly()]
|
||||
throttles = [AnonThrottle(rate='5/min')]
|
||||
from djangorestframework.routers import DefaultResourceRouter
|
||||
from models import BlogPost
|
||||
|
||||
class CommentResource(ModelResource):
|
||||
serializer_class = CommentSerializer
|
||||
model = Comment
|
||||
permissions = [AdminOrAnonReadonly()]
|
||||
throttles = [AnonThrottle(rate='5/min')]
|
||||
class BlogPostResource (ModelResource):
|
||||
pass
|
||||
|
||||
Now that we're using Resources rather than Views, we don't need to design the urlconf ourselves. The conventions for wiring up resources into views and urls are handled automatically. All we need to do is register the appropriate resources with a router, and let it do the rest. Here's our re-wired `urls.py` file.
|
||||
class CommentResource (ModelResource):
|
||||
pass
|
||||
|
||||
from blog import resources
|
||||
from djangorestframework.routers import DefaultRouter
|
||||
api = DefaultResourceRouter()
|
||||
api.register(BlogPost, BlogPostResource)
|
||||
api.register(Comment, CommentResource)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(resources.BlogPostResource)
|
||||
router.register(resources.CommentResource)
|
||||
urlpatterns = router.urlpatterns
|
||||
urls.py
|
||||
|
||||
from resources import api
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url('^', import(api.urls))
|
||||
)
|
||||
|
||||
### Do you need a resource at all?
|
||||
|
||||
In the preceding example, the `Resource` classes don't define any custom values
|
||||
(yet). As a result, the default model resource will be provided. If you are
|
||||
happy with the default resource, you don't need to define a `Resource`
|
||||
object at all -- you can register the resource without providing a `Resource`
|
||||
description. The preceding example could be simplified to:
|
||||
|
||||
from djangorestframework.routers import DefaultResourceRouter
|
||||
from models import BlogPost
|
||||
|
||||
api = DefaultResourceRouter()
|
||||
api.register(BlogPost)
|
||||
api.register(CommentPost)
|
||||
|
||||
## ModelResource options
|
||||
|
||||
*ModelResource.serializer_class*
|
||||
|
||||
Defaults to `ModelSerializer`.
|
||||
|
||||
*ModelResource.permissions*
|
||||
|
||||
Defaults to `DEFAULT_PERMISSIONS`.
|
||||
|
||||
*ModelResource.throttles*
|
||||
|
||||
Defaults to `DEFAULT_THROTTLES`.
|
||||
|
||||
*ModelResource.list_view_class*
|
||||
|
||||
Defaults to `RootAPIView`. Set to `ListAPIView` for read-only.
|
||||
|
||||
*ModelResource.instance_view_class*
|
||||
|
||||
Defaults to `InstanceAPIView`. Set to `DetailAPIView` for read-only.
|
||||
|
||||
*ModelResource.collection_name*
|
||||
|
||||
If `None`, the model's `verbose_name_plural` will be used.
|
||||
|
||||
*ModelResource.id_field_name*
|
||||
|
||||
Defaults to `'pk'`.
|
||||
|
||||
## Trade-offs between views vs resources.
|
||||
|
||||
|
@ -46,4 +82,4 @@ We've reached the end of our tutorial. If you want to get more involved in the
|
|||
* Join the REST framework group, and help build the community.
|
||||
* Follow me [on Twitter](https://twitter.com/_tomchristie) and say hi.
|
||||
|
||||
Now go build something great.
|
||||
Now go build something great.
|
||||
|
|
Loading…
Reference in New Issue
Block a user