From 2ad1fce536ea7411c492ca39e8bdc873f4a7d3b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sat, 18 May 2013 17:50:09 +0200 Subject: [PATCH 01/31] Adding first prototype of a List/Create API view. --- djadmin2/apiviews.py | 18 ++++++++++++++++++ djadmin2/core.py | 10 +++++----- djadmin2/models.py | 19 ++++++++++++++++++- 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 djadmin2/apiviews.py diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py new file mode 100644 index 0000000..14bd2ed --- /dev/null +++ b/djadmin2/apiviews.py @@ -0,0 +1,18 @@ +from rest_framework.fields import Field +from rest_framework.generics import ListCreateAPIView +from rest_framework.serializers import ModelSerializer + + +class Admin2APISerializer(ModelSerializer): + unicode = Field(source='__unicode__') + + +class ModelListCreateAPIView(ListCreateAPIView): + def get_serializer_class(self): + if self.serializer_class is None: + class ModelAPISerilizer(Admin2APISerializer): + class Meta: + model = self.model + + return ModelAPISerilizer + return super(ModelListCreateAPIView, self).get_serializer_class() diff --git a/djadmin2/core.py b/djadmin2/core.py index 8e2138c..8cf21c0 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -83,18 +83,18 @@ class Admin2(object): def get_urls(self): urlpatterns = patterns('', - url(r'^$', self.index_view.as_view(**self.get_index_kwargs()), name='dashboard'), + url(r'^$', self.index_view.as_view(), name='index'), ) for model, modeladmin in self.registry.iteritems(): - app_label = model._meta.app_label - model_name = model._meta.object_name.lower() - urlpatterns += patterns('', - url('^{}/{}/'.format(app_label, model_name), + url('^{}/{}/'.format(model._meta.app_label, model._meta.object_name.lower()), include(modeladmin.urls)), + url('^api/{}/{}/'.format(model._meta.app_label, model._meta.object_name.lower()), + include(modeladmin.api_urls)), ) return urlpatterns @property def urls(self): + # We set the application and instance namespace here return self.get_urls(), self.name, self.name diff --git a/djadmin2/models.py b/djadmin2/models.py index 60787d5..8f4d187 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -5,6 +5,8 @@ synonymous with the django.contrib.admin.sites model. """ +from djadmin2 import apiviews +from djadmin2 import views from django.core.urlresolvers import reverse from django.conf.urls import patterns, url @@ -108,6 +110,8 @@ class ModelAdmin2(BaseAdmin2): detail_view = views.ModelDetailView delete_view = views.ModelDeleteView + api_index_view = apiviews.ModelListCreateAPIView + def __init__(self, model, **kwargs): self.model = model self.app_label = model._meta.app_label @@ -184,11 +188,24 @@ class ModelAdmin2(BaseAdmin2): ), ) + def get_api_urls(self): + return patterns('', + url( + regex=r'^$', + view=self.api_index_view.as_view(model=self.model), + name='api-index' + ), + ) + @property def urls(self): - # We set the application and instance namespace here return self.get_urls(), None, None + @property + def api_urls(self): + return self.get_api_urls(), None, None + + def create_extra_permissions(app, created_models, verbosity, **kwargs): """ Creates 'view' permissions for all models. From 1c77a0a740c432fcb33519588df65205c085c055 Mon Sep 17 00:00:00 2001 From: Ludvig Wadenstein Date: Sat, 18 May 2013 18:23:44 +0200 Subject: [PATCH 02/31] Changed blog.tests into a directory. WARNING: remove test.pyc --- example/blog/tests/__init__.py | 2 ++ example/blog/{tests.py => tests/test_views.py} | 0 2 files changed, 2 insertions(+) create mode 100644 example/blog/tests/__init__.py rename example/blog/{tests.py => tests/test_views.py} (100%) diff --git a/example/blog/tests/__init__.py b/example/blog/tests/__init__.py new file mode 100644 index 0000000..9ccb8bb --- /dev/null +++ b/example/blog/tests/__init__.py @@ -0,0 +1,2 @@ +from test_views import * +from test_apiviews import * diff --git a/example/blog/tests.py b/example/blog/tests/test_views.py similarity index 100% rename from example/blog/tests.py rename to example/blog/tests/test_views.py From bfc61eadcbc40878fa5d6133298a48c6f52ea94f Mon Sep 17 00:00:00 2001 From: Ludvig Wadenstein Date: Sat, 18 May 2013 18:24:21 +0200 Subject: [PATCH 03/31] Added basic test for api index view --- example/blog/tests/test_apiviews.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 example/blog/tests/test_apiviews.py diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py new file mode 100644 index 0000000..463b13d --- /dev/null +++ b/example/blog/tests/test_apiviews.py @@ -0,0 +1,18 @@ +from django.utils import unittest +from django.test.client import RequestFactory + + +from djadmin2 import apiviews +from ..models import Post + + +class ViewTest(unittest.TestCase): + def setUp(self): + self.factory = RequestFactory() + + +class IndexViewModelListCreateAPIViewTest(ViewTest): + def test_response_ok(self): + request = self.factory.get('/admin/api/blog/post/') + response = apiviews.ModelListCreateAPIView.as_view(model=Post)(request) + self.assertEqual(response.status_code, 200) From 9db48cf0d9088cad09cf60e42db054e6d9ef14a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sat, 18 May 2013 18:28:26 +0200 Subject: [PATCH 04/31] Adding a detail based view. --- djadmin2/apiviews.py | 20 ++++++++++++++------ djadmin2/models.py | 14 +++++++++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index 14bd2ed..52df071 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -1,13 +1,13 @@ -from rest_framework.fields import Field -from rest_framework.generics import ListCreateAPIView -from rest_framework.serializers import ModelSerializer +from rest_framework import fields, generics, serializers -class Admin2APISerializer(ModelSerializer): - unicode = Field(source='__unicode__') +class Admin2APISerializer(serializers.ModelSerializer): + unicode = fields.Field(source='__unicode__') -class ModelListCreateAPIView(ListCreateAPIView): +class Admin2APIMixin(object): + modeladmin = None + def get_serializer_class(self): if self.serializer_class is None: class ModelAPISerilizer(Admin2APISerializer): @@ -16,3 +16,11 @@ class ModelListCreateAPIView(ListCreateAPIView): return ModelAPISerilizer return super(ModelListCreateAPIView, self).get_serializer_class() + + +class ModelListCreateAPIView(Admin2APIMixin, generics.ListCreateAPIView): + pass + + +class ModelRetrieveUpdateDestroyAPIView(Admin2APIMixin, generics.RetrieveUpdateDestroyAPIView): + pass diff --git a/djadmin2/models.py b/djadmin2/models.py index 8f4d187..fcf96af 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -111,6 +111,7 @@ class ModelAdmin2(BaseAdmin2): delete_view = views.ModelDeleteView api_index_view = apiviews.ModelListCreateAPIView + api_detail_view = apiviews.ModelRetrieveUpdateDestroyAPIView def __init__(self, model, **kwargs): self.model = model @@ -156,6 +157,12 @@ class ModelAdmin2(BaseAdmin2): def get_delete_kwargs(self): return self.get_default_view_kwargs() + def get_api_index_kwargs(self): + return self.get_default_view_kwargs() + + def get_api_detail_kwargs(self): + return self.get_default_view_kwargs() + def get_index_url(self): return reverse('admin2:{}'.format(self.get_prefixed_view_name('index'))) @@ -192,9 +199,14 @@ class ModelAdmin2(BaseAdmin2): return patterns('', url( regex=r'^$', - view=self.api_index_view.as_view(model=self.model), + view=self.api_index_view.as_view(**self.get_api_index_kwargs()), name='api-index' ), + url( + regex=r'^(?P[0-9]+)/$', + view=self.api_detail_view.as_view(**self.get_api_detail_kwargs()), + name='api-detail' + ), ) @property From 7754cae8948057330cba0ece13be62a6973f4c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 09:34:22 +0200 Subject: [PATCH 05/31] Fixing tests for API urls. --- djadmin2/tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djadmin2/tests/test_core.py b/djadmin2/tests/test_core.py index bd9397b..f8dc588 100644 --- a/djadmin2/tests/test_core.py +++ b/djadmin2/tests/test_core.py @@ -33,4 +33,4 @@ class Admin2Test(unittest.TestCase): def test_get_urls(self): self.admin2.register(Thing) - self.assertEquals(2, len(self.admin2.get_urls())) + self.assertEquals(3, len(self.admin2.get_urls())) From 2a28417f0f2a85dc17dd060ba27a49207238a523 Mon Sep 17 00:00:00 2001 From: Ludvig Wadenstein Date: Sun, 19 May 2013 10:13:30 +0200 Subject: [PATCH 06/31] Added test for unicode field in rest api --- example/blog/tests/test_apiviews.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 463b13d..3040c2e 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -12,7 +12,16 @@ class ViewTest(unittest.TestCase): class IndexViewModelListCreateAPIViewTest(ViewTest): + def test_response_ok(self): request = self.factory.get('/admin/api/blog/post/') response = apiviews.ModelListCreateAPIView.as_view(model=Post)(request) self.assertEqual(response.status_code, 200) + + def test_list_includes_unicode_field(self): + Post.objects.create(title='Foo', body='Bar') + request = self.factory.get('/admin/api/blog/post/') + response = apiviews.ModelListCreateAPIView.as_view(model=Post)(request) + response.render() + + self.assertIn('"unicode": "Foo"', response.content) From f0c86f10b1d41923ad0c7dcf5bcd046f86bbdccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sat, 18 May 2013 17:50:09 +0200 Subject: [PATCH 07/31] Adding first prototype of a List/Create API view. --- djadmin2/core.py | 1 + djadmin2/models.py | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/djadmin2/core.py b/djadmin2/core.py index 8cf21c0..65004ae 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -67,6 +67,7 @@ class Admin2(object): Autodiscovers all admin2.py modules for apps in INSTALLED_APPS by trying to import them. """ + apps = [] for app_name in [x for x in settings.INSTALLED_APPS]: try: import_module("%s.admin2" % app_name) diff --git a/djadmin2/models.py b/djadmin2/models.py index fcf96af..355e131 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -5,14 +5,12 @@ synonymous with the django.contrib.admin.sites model. """ -from djadmin2 import apiviews -from djadmin2 import views - from django.core.urlresolvers import reverse from django.conf.urls import patterns, url from django.contrib.auth import models as auth_app from django.db.models import get_models, signals +from djadmin2 import apiviews from djadmin2 import views try: @@ -110,6 +108,7 @@ class ModelAdmin2(BaseAdmin2): detail_view = views.ModelDetailView delete_view = views.ModelDeleteView + # API Views api_index_view = apiviews.ModelListCreateAPIView api_detail_view = apiviews.ModelRetrieveUpdateDestroyAPIView @@ -200,12 +199,12 @@ class ModelAdmin2(BaseAdmin2): url( regex=r'^$', view=self.api_index_view.as_view(**self.get_api_index_kwargs()), - name='api-index' + name=self.get_prefixed_view_name('api-index'), ), url( regex=r'^(?P[0-9]+)/$', view=self.api_detail_view.as_view(**self.get_api_detail_kwargs()), - name='api-detail' + name=self.get_prefixed_view_name('api-detail'), ), ) From 9f25a3671a066781ae5222b71032dac4bf7b9660 Mon Sep 17 00:00:00 2001 From: Ludvig Wadenstein Date: Sat, 18 May 2013 18:24:21 +0200 Subject: [PATCH 08/31] Added basic test for api index view --- example/blog/tests/test_apiviews.py | 1 - 1 file changed, 1 deletion(-) diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 3040c2e..c36d238 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -12,7 +12,6 @@ class ViewTest(unittest.TestCase): class IndexViewModelListCreateAPIViewTest(ViewTest): - def test_response_ok(self): request = self.factory.get('/admin/api/blog/post/') response = apiviews.ModelListCreateAPIView.as_view(model=Post)(request) From d5330e14f62d245bdf97d468fad51952ab7fec13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sat, 18 May 2013 18:28:26 +0200 Subject: [PATCH 09/31] Adding a detail based view. --- djadmin2/core.py | 2 +- djadmin2/models.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/djadmin2/core.py b/djadmin2/core.py index 65004ae..f16c750 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -90,7 +90,7 @@ class Admin2(object): urlpatterns += patterns('', url('^{}/{}/'.format(model._meta.app_label, model._meta.object_name.lower()), include(modeladmin.urls)), - url('^api/{}/{}/'.format(model._meta.app_label, model._meta.object_name.lower()), + url('^api/{}/{}/'.format(app_label, model_name), include(modeladmin.api_urls)), ) return urlpatterns diff --git a/djadmin2/models.py b/djadmin2/models.py index 355e131..94c6d3b 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -165,6 +165,12 @@ class ModelAdmin2(BaseAdmin2): def get_index_url(self): return reverse('admin2:{}'.format(self.get_prefixed_view_name('index'))) + def get_api_index_kwargs(self): + return self.get_default_view_kwargs() + + def get_api_detail_kwargs(self): + return self.get_default_view_kwargs() + def get_urls(self): return patterns('', url( From 8e2aebcc0943a68daede152b72cf5d01b3b01f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 09:51:09 +0200 Subject: [PATCH 10/31] Renaming a few things to make more sense and to stick to conventions. --- djadmin2/apiviews.py | 8 ++++---- djadmin2/models.py | 4 ++-- example/blog/tests/test_apiviews.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index 52df071..d169f40 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -2,7 +2,7 @@ from rest_framework import fields, generics, serializers class Admin2APISerializer(serializers.ModelSerializer): - unicode = fields.Field(source='__unicode__') + __str__ = fields.Field(source='__unicode__') class Admin2APIMixin(object): @@ -15,12 +15,12 @@ class Admin2APIMixin(object): model = self.model return ModelAPISerilizer - return super(ModelListCreateAPIView, self).get_serializer_class() + return super(Admin2APIMixin, self).get_serializer_class() -class ModelListCreateAPIView(Admin2APIMixin, generics.ListCreateAPIView): +class ListCreateAPIView(Admin2APIMixin, generics.ListCreateAPIView): pass -class ModelRetrieveUpdateDestroyAPIView(Admin2APIMixin, generics.RetrieveUpdateDestroyAPIView): +class RetrieveUpdateDestroyAPIView(Admin2APIMixin, generics.RetrieveUpdateDestroyAPIView): pass diff --git a/djadmin2/models.py b/djadmin2/models.py index 94c6d3b..0e069c1 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -109,8 +109,8 @@ class ModelAdmin2(BaseAdmin2): delete_view = views.ModelDeleteView # API Views - api_index_view = apiviews.ModelListCreateAPIView - api_detail_view = apiviews.ModelRetrieveUpdateDestroyAPIView + api_index_view = apiviews.ListCreateAPIView + api_detail_view = apiviews.RetrieveUpdateDestroyAPIView def __init__(self, model, **kwargs): self.model = model diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index c36d238..9e10dc7 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -14,7 +14,7 @@ class ViewTest(unittest.TestCase): class IndexViewModelListCreateAPIViewTest(ViewTest): def test_response_ok(self): request = self.factory.get('/admin/api/blog/post/') - response = apiviews.ModelListCreateAPIView.as_view(model=Post)(request) + response = apiviews.ListCreateAPIView.as_view(model=Post)(request) self.assertEqual(response.status_code, 200) def test_list_includes_unicode_field(self): From c7f935cd1cb512625447a99f75d044cf734729a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 10:07:16 +0200 Subject: [PATCH 11/31] Adding a quick hint to the API in the documentation. --- docs/api.rst | 16 ++++++++++++++++ docs/index.rst | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 docs/api.rst diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..78ae92c --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,16 @@ +API +=== + +**django-admin2** comes with a builtin REST-API for accessing all the +resources you can get from the frontend via JSON. + +The API can be found at the URL you choose for the admin2 and then append +``api/v0/``. + +If the API has changed in a backwards-incompatible way we will increase the +API version to the next number. So you can be sure that you're frontend code +should keep working even between updates to more recent django-admin2 +versions. + +However currently we are still in heavy development, so we are using ``v0`` +for the API, which means is subject to change and being broken at any time. diff --git a/docs/index.rst b/docs/index.rst index 503c556..c5e2db7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -61,6 +61,7 @@ Content :maxdepth: 2 contributing + api design meta @@ -71,4 +72,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - From 944a846eb321875d8a187533bacd542396ed1351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 10:09:04 +0200 Subject: [PATCH 12/31] Adding rest_framework to the INSTALLED_APPS so that the explorable API browser works. --- example/example/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/example/example/settings.py b/example/example/settings.py index 6de83ad..d2ffcb4 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -121,6 +121,7 @@ INSTALLED_APPS = ( # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'django_coverage', + 'rest_framework', 'djadmin2', 'blog', ) From 0e559347d39baa443e6cdc638cf1e22405de1804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 10:15:56 +0200 Subject: [PATCH 13/31] Versioning the API. --- djadmin2/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djadmin2/core.py b/djadmin2/core.py index f16c750..2303136 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -90,7 +90,7 @@ class Admin2(object): urlpatterns += patterns('', url('^{}/{}/'.format(model._meta.app_label, model._meta.object_name.lower()), include(modeladmin.urls)), - url('^api/{}/{}/'.format(app_label, model_name), + url('^api/v0/{}/{}/'.format(app_label, model_name), include(modeladmin.api_urls)), ) return urlpatterns From 6cc5b5e18a7c31139c8f5213f70af9c4a0077de9 Mon Sep 17 00:00:00 2001 From: Ludvig Wadenstein Date: Sun, 19 May 2013 10:13:30 +0200 Subject: [PATCH 14/31] Added test for unicode field in rest api --- example/blog/tests/test_apiviews.py | 1 + 1 file changed, 1 insertion(+) diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 9e10dc7..64ea11a 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -12,6 +12,7 @@ class ViewTest(unittest.TestCase): class IndexViewModelListCreateAPIViewTest(ViewTest): + def test_response_ok(self): request = self.factory.get('/admin/api/blog/post/') response = apiviews.ListCreateAPIView.as_view(model=Post)(request) From dae15811ee0eee39a3d40ac8ab9ede60cae01a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 10:38:10 +0200 Subject: [PATCH 15/31] Fixing urls. --- djadmin2/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djadmin2/models.py b/djadmin2/models.py index 0e069c1..59fe2f3 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -216,13 +216,13 @@ class ModelAdmin2(BaseAdmin2): @property def urls(self): + # We set the application and instance namespace here return self.get_urls(), None, None @property def api_urls(self): return self.get_api_urls(), None, None - def create_extra_permissions(app, created_models, verbosity, **kwargs): """ Creates 'view' permissions for all models. From 58e5f7b54c49e5da4fdffb5151e4574d6c681225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 10:40:46 +0200 Subject: [PATCH 16/31] Using admin views mixin for API views. --- djadmin2/apiviews.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index d169f40..67ee5fe 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -1,18 +1,18 @@ from rest_framework import fields, generics, serializers +from .views import Admin2Mixin class Admin2APISerializer(serializers.ModelSerializer): __str__ = fields.Field(source='__unicode__') -class Admin2APIMixin(object): - modeladmin = None - +class Admin2APIMixin(Admin2Mixin): def get_serializer_class(self): if self.serializer_class is None: + model_class = self.get_model() class ModelAPISerilizer(Admin2APISerializer): class Meta: - model = self.model + model = model_class return ModelAPISerilizer return super(Admin2APIMixin, self).get_serializer_class() From 585d4d9e8e62c683011379572f8b431c9041202a Mon Sep 17 00:00:00 2001 From: Ludvig Wadenstein Date: Sun, 19 May 2013 10:56:11 +0200 Subject: [PATCH 17/31] Just commiting to be able to merge --- example/blog/tests/test_apiviews.py | 18 ++++++++++++++---- example/blog/tests/test_views.py | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 64ea11a..29fad97 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -1,5 +1,6 @@ from django.utils import unittest from django.test.client import RequestFactory +from django.core.urlresolvers import reverse from djadmin2 import apiviews @@ -11,17 +12,26 @@ class ViewTest(unittest.TestCase): self.factory = RequestFactory() -class IndexViewModelListCreateAPIViewTest(ViewTest): +class ModelListCreateAPIViewTest(ViewTest): def test_response_ok(self): - request = self.factory.get('/admin/api/blog/post/') - response = apiviews.ListCreateAPIView.as_view(model=Post)(request) + request = self.factory.get(reverse('admin2:api-index', args=['blog', 'post'])) + response = apiviews.ModelListCreateAPIView.as_view(model=Post)(request) self.assertEqual(response.status_code, 200) def test_list_includes_unicode_field(self): Post.objects.create(title='Foo', body='Bar') - request = self.factory.get('/admin/api/blog/post/') + request = self.factory.get(reverse('admin2:api-index'), args=['blog', 'post']) response = apiviews.ModelListCreateAPIView.as_view(model=Post)(request) response.render() self.assertIn('"unicode": "Foo"', response.content) + + +class ModelRetrieveUpdateDestroyAPIViewTest(ViewTest): + + def test_response_ok(self): + post = Post.objects.create(title='Foo', body='Bar') + request = self.factory.get(reverse('admin2:api-detail', args=['blog', 'post', post.pk])) + response = apiviews.ModelRetrieveUpdateDestroyAPIView.as_view(model=Post)(request) + self.assertEqual(response.status_code, 200) diff --git a/example/blog/tests/test_views.py b/example/blog/tests/test_views.py index e2d7a14..7c8e20e 100644 --- a/example/blog/tests/test_views.py +++ b/example/blog/tests/test_views.py @@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from django.test import TestCase, Client -from .models import Post +from ..models import Post class BaseIntegrationTest(TestCase): From c7014282dcf946f40f41e279fef3deb0978ba9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 10:56:31 +0200 Subject: [PATCH 18/31] Renaming 'api-index' view to 'api-list' makes more sense, since the index will be a list of all available models. --- djadmin2/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djadmin2/models.py b/djadmin2/models.py index 59fe2f3..b871a40 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -205,7 +205,7 @@ class ModelAdmin2(BaseAdmin2): url( regex=r'^$', view=self.api_index_view.as_view(**self.get_api_index_kwargs()), - name=self.get_prefixed_view_name('api-index'), + name=self.get_prefixed_view_name('api-list'), ), url( regex=r'^(?P[0-9]+)/$', From a16faccade7dbf0206723b95025ae1e927966701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 11:08:25 +0200 Subject: [PATCH 19/31] Hyperlinking the API. --- djadmin2/apiviews.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index 67ee5fe..203e7e2 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -2,7 +2,10 @@ from rest_framework import fields, generics, serializers from .views import Admin2Mixin -class Admin2APISerializer(serializers.ModelSerializer): +class Admin2APISerializer(serializers.HyperlinkedModelSerializer): + _default_view_name = 'admin2:api_%(app_label)s_%(model_name)s_detail' + + pk = fields.Field(source='pk') __str__ = fields.Field(source='__unicode__') From 09a1f2465f562adeb3eb65d6b096fd68001c6622 Mon Sep 17 00:00:00 2001 From: Ludvig Wadenstein Date: Sun, 19 May 2013 11:08:36 +0200 Subject: [PATCH 20/31] Added test for detail view in api --- example/blog/tests/test_apiviews.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 29fad97..1a03430 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -12,26 +12,26 @@ class ViewTest(unittest.TestCase): self.factory = RequestFactory() -class ModelListCreateAPIViewTest(ViewTest): +class ListCreateAPIViewTest(ViewTest): def test_response_ok(self): - request = self.factory.get(reverse('admin2:api-index', args=['blog', 'post'])) - response = apiviews.ModelListCreateAPIView.as_view(model=Post)(request) + request = self.factory.get(reverse('admin2:blog_post_api-index')) + response = apiviews.ListCreateAPIView.as_view(model=Post)(request) self.assertEqual(response.status_code, 200) def test_list_includes_unicode_field(self): Post.objects.create(title='Foo', body='Bar') - request = self.factory.get(reverse('admin2:api-index'), args=['blog', 'post']) - response = apiviews.ModelListCreateAPIView.as_view(model=Post)(request) + request = self.factory.get(reverse('admin2:blog_post_api-index')) + response = apiviews.ListCreateAPIView.as_view(model=Post)(request) response.render() - self.assertIn('"unicode": "Foo"', response.content) + self.assertIn('"__str__": "Foo"', response.content) -class ModelRetrieveUpdateDestroyAPIViewTest(ViewTest): +class RetrieveUpdateDestroyAPIViewTest(ViewTest): def test_response_ok(self): post = Post.objects.create(title='Foo', body='Bar') - request = self.factory.get(reverse('admin2:api-detail', args=['blog', 'post', post.pk])) - response = apiviews.ModelRetrieveUpdateDestroyAPIView.as_view(model=Post)(request) + request = self.factory.get(reverse('admin2:blog_post_api-detail', kwargs={'pk': post.pk})) + response = apiviews.RetrieveUpdateDestroyAPIView.as_view(model=Post)(request, pk=post.pk) self.assertEqual(response.status_code, 200) From 68d6b96f8415efd6c06bb526145daa3ef94cda8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 11:45:44 +0200 Subject: [PATCH 21/31] Adding an index resource for the API. --- djadmin2/apiviews.py | 43 +++++++++++++++++++++++++++++++++++++++++- djadmin2/core.py | 10 +++++++++- example/blog/admin2.py | 4 +++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index 203e7e2..2991e0e 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -1,9 +1,15 @@ +from django.utils.encoding import force_str from rest_framework import fields, generics, serializers +from rest_framework.response import Response +from rest_framework.reverse import reverse +from rest_framework.views import APIView from .views import Admin2Mixin +API_VERSION = '0.1' + class Admin2APISerializer(serializers.HyperlinkedModelSerializer): - _default_view_name = 'admin2:api_%(app_label)s_%(model_name)s_detail' + _default_view_name = 'admin2:%(app_label)s_%(model_name)s_api-detail' pk = fields.Field(source='pk') __str__ = fields.Field(source='__unicode__') @@ -21,6 +27,41 @@ class Admin2APIMixin(Admin2Mixin): return super(Admin2APIMixin, self).get_serializer_class() +class IndexAPIView(Admin2APIMixin, APIView): + registry = None + + def get_model_data(self, model, modeladmin): + opts = { + 'app_label': model._meta.app_label, + 'model_name': model._meta.object_name.lower(), + } + model_url = reverse( + 'admin2:%(app_label)s_%(model_name)s_api-list' % opts, + request=self.request, + format=self.kwargs.get('format')) + return { + 'url': model_url, + 'verbose_name': force_str(model._meta.verbose_name), + 'verbose_name_plural': force_str(model._meta.verbose_name_plural), + } + + def get(self, request): + index_data = { + 'version': API_VERSION, + 'apps': [], + } + for model, modeladmin in self.registry.items(): + app_data = { + 'url': '-- todo --', + 'app_label': '-- todo --', + 'models': [ + self.get_model_data(model, modeladmin), + ], + } + index_data['apps'].append(app_data) + return Response(index_data) + + class ListCreateAPIView(Admin2APIMixin, generics.ListCreateAPIView): pass diff --git a/djadmin2/core.py b/djadmin2/core.py index 2303136..1afd8be 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -4,6 +4,7 @@ from django.core.exceptions import ImproperlyConfigured from django.utils.importlib import import_module +from . import apiviews from . import models from . import views @@ -17,6 +18,7 @@ class Admin2(object): It also provides an index view that serves as an entry point to the admin site. """ index_view = views.IndexView + api_index_view = apiviews.IndexAPIView def __init__(self, name='admin2'): self.registry = {} @@ -82,9 +84,15 @@ class Admin2(object): 'apps': self.apps, } + def get_api_index_kwargs(self): + return { + 'registry': self.registry, + } + def get_urls(self): urlpatterns = patterns('', - url(r'^$', self.index_view.as_view(), name='index'), + url(r'^$', self.index_view.as_view(**self.get_index_kwargs()), name='dashboard'), + url(r'^api/v0/$', self.api_index_view.as_view(**self.get_index_kwargs()), name='api'), ) for model, modeladmin in self.registry.iteritems(): urlpatterns += patterns('', diff --git a/example/blog/admin2.py b/example/blog/admin2.py index e2cbad3..8367587 100644 --- a/example/blog/admin2.py +++ b/example/blog/admin2.py @@ -2,7 +2,7 @@ # Import your custom models from .models import Post, Comment from django.contrib.auth.forms import UserCreationForm, UserChangeForm -from django.contrib.auth.models import User +from django.contrib.auth.models import Group, Permission, User import djadmin2 @@ -18,3 +18,5 @@ class UserAdmin2(ModelAdmin2): djadmin2.default.register(Post) djadmin2.default.register(Comment) djadmin2.default.register(User, UserAdmin2) +djadmin2.default.register(Permission) +djadmin2.default.register(Group) From 73a41f56326de3dacdef8cec86a39120b81eb231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 12:08:42 +0200 Subject: [PATCH 22/31] Fixing tests for API. --- djadmin2/apiviews.py | 11 ++++++++++- djadmin2/core.py | 2 +- djadmin2/models.py | 7 ++++--- djadmin2/tests/test_core.py | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index 2991e0e..1a896cf 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -19,7 +19,15 @@ class Admin2APIMixin(Admin2Mixin): def get_serializer_class(self): if self.serializer_class is None: model_class = self.get_model() + class ModelAPISerilizer(Admin2APISerializer): + # we need to reset this here, since we don't know anything + # about the name of the admin instance when declaring the + # Admin2APISerializer base class + _default_view_name = ':'.join(( + self.modeladmin.admin.name + + '%(app_label)s_%(model_name)s_api-detail')) + class Meta: model = model_class @@ -32,11 +40,12 @@ class IndexAPIView(Admin2APIMixin, APIView): def get_model_data(self, model, modeladmin): opts = { + 'current_app': modeladmin.admin.name, 'app_label': model._meta.app_label, 'model_name': model._meta.object_name.lower(), } model_url = reverse( - 'admin2:%(app_label)s_%(model_name)s_api-list' % opts, + '%(current_app)s:%(app_label)s_%(model_name)s_api-list' % opts, request=self.request, format=self.kwargs.get('format')) return { diff --git a/djadmin2/core.py b/djadmin2/core.py index 1afd8be..2da9c23 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -42,7 +42,7 @@ class Admin2(object): raise ImproperlyConfigured('%s is already registered in django-admin2' % model) if not modeladmin: modeladmin = models.ModelAdmin2 - self.registry[model] = modeladmin(model, **kwargs) + self.registry[model] = modeladmin(model, admin=self, **kwargs) # Add the model to the apps registry app_label = model._meta.app_label diff --git a/djadmin2/models.py b/djadmin2/models.py index b871a40..683360e 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -48,10 +48,10 @@ class BaseAdmin2(object): readonly_fields = () ordering = None - def __init__(self, model): + def __init__(self, model, admin): super(BaseAdmin2, self).__init__() - self.model = model + self.admin = admin def _user_has_permission(self, user, permission_type, obj=None): """ Generic method for checking whether the user has permission of specified type for the model. @@ -112,8 +112,9 @@ class ModelAdmin2(BaseAdmin2): api_index_view = apiviews.ListCreateAPIView api_detail_view = apiviews.RetrieveUpdateDestroyAPIView - def __init__(self, model, **kwargs): + def __init__(self, model, admin, **kwargs): self.model = model + self.admin = admin self.app_label = model._meta.app_label self.model_name = model._meta.object_name.lower() diff --git a/djadmin2/tests/test_core.py b/djadmin2/tests/test_core.py index f8dc588..8713543 100644 --- a/djadmin2/tests/test_core.py +++ b/djadmin2/tests/test_core.py @@ -33,4 +33,4 @@ class Admin2Test(unittest.TestCase): def test_get_urls(self): self.admin2.register(Thing) - self.assertEquals(3, len(self.admin2.get_urls())) + self.assertEquals(4, len(self.admin2.get_urls())) From 09dd2095e3614f0b5389d859f3e33106cdd149e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 12:27:51 +0200 Subject: [PATCH 23/31] Fixing names for views beeing more consistent and tests for API views. --- djadmin2/apiviews.py | 2 +- djadmin2/models.py | 6 +++--- example/blog/tests/test_apiviews.py | 28 ++++++++++++++++++++++------ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index 1a896cf..c4042dc 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -25,7 +25,7 @@ class Admin2APIMixin(Admin2Mixin): # about the name of the admin instance when declaring the # Admin2APISerializer base class _default_view_name = ':'.join(( - self.modeladmin.admin.name + + self.modeladmin.admin.name, '%(app_label)s_%(model_name)s_api-detail')) class Meta: diff --git a/djadmin2/models.py b/djadmin2/models.py index 683360e..6765df9 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -109,7 +109,7 @@ class ModelAdmin2(BaseAdmin2): delete_view = views.ModelDeleteView # API Views - api_index_view = apiviews.ListCreateAPIView + api_list_view = apiviews.ListCreateAPIView api_detail_view = apiviews.RetrieveUpdateDestroyAPIView def __init__(self, model, admin, **kwargs): @@ -166,7 +166,7 @@ class ModelAdmin2(BaseAdmin2): def get_index_url(self): return reverse('admin2:{}'.format(self.get_prefixed_view_name('index'))) - def get_api_index_kwargs(self): + def get_api_list_kwargs(self): return self.get_default_view_kwargs() def get_api_detail_kwargs(self): @@ -205,7 +205,7 @@ class ModelAdmin2(BaseAdmin2): return patterns('', url( regex=r'^$', - view=self.api_index_view.as_view(**self.get_api_index_kwargs()), + view=self.api_list_view.as_view(**self.get_api_list_kwargs()), name=self.get_prefixed_view_name('api-list'), ), url( diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 1a03430..ad42207 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -4,6 +4,8 @@ from django.core.urlresolvers import reverse from djadmin2 import apiviews +from djadmin2 import default +from djadmin2.models import ModelAdmin2 from ..models import Post @@ -11,18 +13,27 @@ class ViewTest(unittest.TestCase): def setUp(self): self.factory = RequestFactory() + def get_model_admin(self, model): + return ModelAdmin2(model, default) + class ListCreateAPIViewTest(ViewTest): def test_response_ok(self): - request = self.factory.get(reverse('admin2:blog_post_api-index')) - response = apiviews.ListCreateAPIView.as_view(model=Post)(request) + request = self.factory.get(reverse('admin2:blog_post_api-list')) + modeladmin = self.get_model_admin(Post) + view = apiviews.ListCreateAPIView.as_view( + **modeladmin.get_api_list_kwargs()) + response = view(request) self.assertEqual(response.status_code, 200) def test_list_includes_unicode_field(self): Post.objects.create(title='Foo', body='Bar') - request = self.factory.get(reverse('admin2:blog_post_api-index')) - response = apiviews.ListCreateAPIView.as_view(model=Post)(request) + request = self.factory.get(reverse('admin2:blog_post_api-list')) + modeladmin = self.get_model_admin(Post) + view = apiviews.ListCreateAPIView.as_view( + **modeladmin.get_api_list_kwargs()) + response = view(request) response.render() self.assertIn('"__str__": "Foo"', response.content) @@ -32,6 +43,11 @@ class RetrieveUpdateDestroyAPIViewTest(ViewTest): def test_response_ok(self): post = Post.objects.create(title='Foo', body='Bar') - request = self.factory.get(reverse('admin2:blog_post_api-detail', kwargs={'pk': post.pk})) - response = apiviews.RetrieveUpdateDestroyAPIView.as_view(model=Post)(request, pk=post.pk) + request = self.factory.get( + reverse('admin2:blog_post_api-detail', + kwargs={'pk': post.pk})) + modeladmin = self.get_model_admin(Post) + view = apiviews.RetrieveUpdateDestroyAPIView.as_view( + **modeladmin.get_api_detail_kwargs()) + response = view(request, pk=post.pk) self.assertEqual(response.status_code, 200) From 631727d0f0d4d7ea189181b90ee91dc734b4120e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 12:53:37 +0200 Subject: [PATCH 24/31] Tests for the API index view. --- djadmin2/apiviews.py | 19 +++++++++---------- djadmin2/core.py | 3 ++- example/blog/tests/test_apiviews.py | 9 +++++++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index c4042dc..ab829d7 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -55,19 +55,18 @@ class IndexAPIView(Admin2APIMixin, APIView): } def get(self, request): + model_data = [] + for model, modeladmin in self.registry.items(): + model_data.append(self.get_model_data(model, modeladmin)) + app_data = { + 'url': '-- todo --', + 'app_label': '-- todo --', + 'models': model_data, + } index_data = { 'version': API_VERSION, - 'apps': [], + 'apps': [app_data], } - for model, modeladmin in self.registry.items(): - app_data = { - 'url': '-- todo --', - 'app_label': '-- todo --', - 'models': [ - self.get_model_data(model, modeladmin), - ], - } - index_data['apps'].append(app_data) return Response(index_data) diff --git a/djadmin2/core.py b/djadmin2/core.py index 2da9c23..f35218d 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -92,7 +92,8 @@ class Admin2(object): def get_urls(self): urlpatterns = patterns('', url(r'^$', self.index_view.as_view(**self.get_index_kwargs()), name='dashboard'), - url(r'^api/v0/$', self.api_index_view.as_view(**self.get_index_kwargs()), name='api'), + url(r'^api/v0/$', + self.api_index_view.as_view(**self.get_index_kwargs()), name='api-index'), ) for model, modeladmin in self.registry.iteritems(): urlpatterns += patterns('', diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index ad42207..29c4f11 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -17,6 +17,15 @@ class ViewTest(unittest.TestCase): return ModelAdmin2(model, default) +class IndexAPIViewTest(ViewTest): + + def test_response_ok(self): + request = self.factory.get(reverse('admin2:api-index')) + view = apiviews.IndexAPIView.as_view(**default.get_api_index_kwargs()) + response = view(request) + self.assertEqual(response.status_code, 200) + + class ListCreateAPIViewTest(ViewTest): def test_response_ok(self): From 8ea3b97786c01b0f3413f0fd1508db9fb1ab7b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 13:10:35 +0200 Subject: [PATCH 25/31] Using django's TestCase and instead of the one from unittest. --- djadmin2/tests/test_core.py | 5 ++--- example/blog/tests/test_apiviews.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/djadmin2/tests/test_core.py b/djadmin2/tests/test_core.py index 8713543..15d0881 100644 --- a/djadmin2/tests/test_core.py +++ b/djadmin2/tests/test_core.py @@ -1,7 +1,6 @@ -import unittest - from django.db import models from django.core.exceptions import ImproperlyConfigured +from django.test import TestCase from ..models import ModelAdmin2 from ..core import Admin2 @@ -11,7 +10,7 @@ class Thing(models.Model): pass -class Admin2Test(unittest.TestCase): +class Admin2Test(TestCase): def setUp(self): self.admin2 = Admin2() diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 29c4f11..621b88a 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -1,4 +1,4 @@ -from django.utils import unittest +from django.test import TestCase from django.test.client import RequestFactory from django.core.urlresolvers import reverse @@ -9,7 +9,7 @@ from djadmin2.models import ModelAdmin2 from ..models import Post -class ViewTest(unittest.TestCase): +class ViewTest(TestCase): def setUp(self): self.factory = RequestFactory() From bc899a93892a1c48b3e8ca1d794643d2c4a67862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 13:11:38 +0200 Subject: [PATCH 26/31] Adding support for pagination. --- djadmin2/models.py | 6 +++++- example/blog/tests/test_apiviews.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/djadmin2/models.py b/djadmin2/models.py index 6765df9..a0b40a5 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -167,7 +167,11 @@ class ModelAdmin2(BaseAdmin2): return reverse('admin2:{}'.format(self.get_prefixed_view_name('index'))) def get_api_list_kwargs(self): - return self.get_default_view_kwargs() + kwargs = self.get_default_view_kwargs() + kwargs.update({ + 'paginate_by': self.list_per_page, + }) + return kwargs def get_api_detail_kwargs(self): return self.get_default_view_kwargs() diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 621b88a..8893ba5 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -1,6 +1,7 @@ from django.test import TestCase from django.test.client import RequestFactory from django.core.urlresolvers import reverse +from django.utils import simplejson as json from djadmin2 import apiviews @@ -47,6 +48,22 @@ class ListCreateAPIViewTest(ViewTest): self.assertIn('"__str__": "Foo"', response.content) + def test_pagination(self): + request = self.factory.get(reverse('admin2:blog_post_api-list')) + modeladmin = self.get_model_admin(Post) + view = apiviews.ListCreateAPIView.as_view( + **modeladmin.get_api_list_kwargs()) + response = view(request) + response.render() + data = json.loads(response.content) + self.assertEqual(data['count'], 0) + # next and previous fields exist, but are null because we have no + # content + self.assertTrue('next' in data) + self.assertEqual(data['next'], None) + self.assertTrue('previous' in data) + self.assertEqual(data['previous'], None) + class RetrieveUpdateDestroyAPIViewTest(ViewTest): From 4e03b7ce1634cfc4ee9b67f182b441749f28a509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 13:22:40 +0200 Subject: [PATCH 27/31] Fixing called function for API index view kwargs. --- djadmin2/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djadmin2/core.py b/djadmin2/core.py index f35218d..47d32a5 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -93,7 +93,7 @@ class Admin2(object): urlpatterns = patterns('', url(r'^$', self.index_view.as_view(**self.get_index_kwargs()), name='dashboard'), url(r'^api/v0/$', - self.api_index_view.as_view(**self.get_index_kwargs()), name='api-index'), + self.api_index_view.as_view(**self.get_api_index_kwargs()), name='api-index'), ) for model, modeladmin in self.registry.iteritems(): urlpatterns += patterns('', From fe140bbf39c470e250a6bd1c6289b360109fd891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 13:35:57 +0200 Subject: [PATCH 28/31] Integrating the new app registry so that the API can generate a nice index page. --- djadmin2/apiviews.py | 22 +++++++++++++++------- djadmin2/core.py | 1 + 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index ab829d7..c292f73 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -36,9 +36,11 @@ class Admin2APIMixin(Admin2Mixin): class IndexAPIView(Admin2APIMixin, APIView): + apps = None registry = None - def get_model_data(self, model, modeladmin): + def get_model_data(self, model): + modeladmin = self.registry[model] opts = { 'current_app': modeladmin.admin.name, 'app_label': model._meta.app_label, @@ -54,18 +56,24 @@ class IndexAPIView(Admin2APIMixin, APIView): 'verbose_name_plural': force_str(model._meta.verbose_name_plural), } - def get(self, request): + def get_app_data(self, app_label, models): model_data = [] - for model, modeladmin in self.registry.items(): - model_data.append(self.get_model_data(model, modeladmin)) - app_data = { + for model in models: + model_data.append(self.get_model_data(model)) + return { 'url': '-- todo --', - 'app_label': '-- todo --', + 'app_label': app_label, 'models': model_data, } + + def get(self, request): + app_data = [] + for app_label, registry in self.apps.items(): + models = registry.keys() + app_data.append(self.get_app_data(app_label, models)) index_data = { 'version': API_VERSION, - 'apps': [app_data], + 'apps': app_data, } return Response(index_data) diff --git a/djadmin2/core.py b/djadmin2/core.py index 47d32a5..ca345ca 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -87,6 +87,7 @@ class Admin2(object): def get_api_index_kwargs(self): return { 'registry': self.registry, + 'apps': self.apps, } def get_urls(self): From 0da7584e997122e7bec9348ba4ef2d66f4ff35f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 14:11:07 +0200 Subject: [PATCH 29/31] We don't need detail pages for apps in the API. --- djadmin2/apiviews.py | 1 - 1 file changed, 1 deletion(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index c292f73..d1d09d1 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -61,7 +61,6 @@ class IndexAPIView(Admin2APIMixin, APIView): for model in models: model_data.append(self.get_model_data(model)) return { - 'url': '-- todo --', 'app_label': app_label, 'models': model_data, } From dc0ecdfe0c0a8500676ad775d2c393d4cb98f565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 14:11:30 +0200 Subject: [PATCH 30/31] Adding a link to the API from the admin frontend. --- djadmin2/templates/admin2/bootstrap/base.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/djadmin2/templates/admin2/bootstrap/base.html b/djadmin2/templates/admin2/bootstrap/base.html index 546f26f..df3dc27 100644 --- a/djadmin2/templates/admin2/bootstrap/base.html +++ b/djadmin2/templates/admin2/bootstrap/base.html @@ -16,6 +16,7 @@ Django-Admin2 @@ -31,4 +32,4 @@ {% block extrajs %}{% endblock %} - \ No newline at end of file + From cd625a604670bd82df8b2533298d63e6bf92dd6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Sun, 19 May 2013 14:26:10 +0200 Subject: [PATCH 31/31] Fixing a bug that was introduced during rebasing. --- djadmin2/core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/djadmin2/core.py b/djadmin2/core.py index ca345ca..4aa4c63 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -98,9 +98,13 @@ class Admin2(object): ) for model, modeladmin in self.registry.iteritems(): urlpatterns += patterns('', - url('^{}/{}/'.format(model._meta.app_label, model._meta.object_name.lower()), + url('^{}/{}/'.format( + model._meta.app_label, + model._meta.object_name.lower()), include(modeladmin.urls)), - url('^api/v0/{}/{}/'.format(app_label, model_name), + url('^api/v0/{}/{}/'.format( + model._meta.app_label, + model._meta.object_name.lower()), include(modeladmin.api_urls)), ) return urlpatterns