From c8e55ce01f3df36af2ea670cbfe0ae691cbf7892 Mon Sep 17 00:00:00 2001 From: Cameron Stitt Date: Wed, 21 Aug 2013 10:21:38 -0700 Subject: [PATCH 01/62] Remove utils.six for 1.4.0 compat. Add django.conf.settings --- ajax/conf.py | 1 + ajax/utils.py | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ajax/conf.py b/ajax/conf.py index 9abd004..959ff65 100644 --- a/ajax/conf.py +++ b/ajax/conf.py @@ -1,3 +1,4 @@ +from django.conf import settings from appconf import AppConf diff --git a/ajax/utils.py b/ajax/utils.py index 364dfad..ecd8a38 100644 --- a/ajax/utils.py +++ b/ajax/utils.py @@ -1,7 +1,6 @@ import sys from django.core.exceptions import ImproperlyConfigured -from django.utils import six from django.utils.importlib import import_module @@ -19,10 +18,8 @@ def import_by_path(dotted_path, error_prefix=''): try: module = import_module(module_path) except ImportError as e: - msg = '%sError importing module %s: "%s"' % ( - error_prefix, module_path, e) - six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg), - sys.exc_info()[2]) + raise ImproperlyConfigured('%sError importing module %s: "%s"' % ( + error_prefix, module_path, e)) try: attr = getattr(module, class_name) except AttributeError: From ad4a22798af5c8e6a42ba35e94fa79f02256526f Mon Sep 17 00:00:00 2001 From: Cameron Stitt Date: Mon, 26 Aug 2013 16:50:49 -0700 Subject: [PATCH 02/62] Bring back old files --- ajax/authentication.py | 6 ++++++ ajax/compat.py | 8 ++++++++ ajax/endpoints.py | 10 ++++++---- setup.py | 7 +++++-- 4 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 ajax/authentication.py create mode 100644 ajax/compat.py diff --git a/ajax/authentication.py b/ajax/authentication.py new file mode 100644 index 0000000..8d3ab9f --- /dev/null +++ b/ajax/authentication.py @@ -0,0 +1,6 @@ +class BaseAuthentication(object): + def is_authenticated(self, request, application, method): + if request.user.is_authenticated(): + return True + + return False diff --git a/ajax/compat.py b/ajax/compat.py new file mode 100644 index 0000000..af729ca --- /dev/null +++ b/ajax/compat.py @@ -0,0 +1,8 @@ +import django + +if django.VERSION >= (1, 6): + from django.utils.module_loading import import_by_path + path_to_import = import_by_path +else: + from ajax.utils import import_by_path + path_to_import = import_by_path diff --git a/ajax/endpoints.py b/ajax/endpoints.py index 148832a..3eb16bd 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -5,6 +5,9 @@ from django.utils.encoding import smart_str from django.utils.translation import ugettext_lazy as _ from django.db.models.fields import FieldDoesNotExist + +from ajax.compat import path_to_import +from ajax.conf import settings from ajax.decorators import require_pk from ajax.exceptions import AJAXError, AlreadyRegistered, NotRegistered, \ PrimaryKeyMissing @@ -28,6 +31,8 @@ class ModelEndpoint(object): immutable_fields = [] # List of model fields that are not writable. + authentication = path_to_import(settings.AJAX_AUTHENTICATION) + def __init__(self, application, model, method, **kwargs): self.application = application self.model = model @@ -200,10 +205,7 @@ def authenticate(self, request, application, method): Most likely you will want to lock down who can edit and delete various models. To do this, just override this method in your child class. """ - if request.user.is_authenticated(): - return True - - return False + return self.authentication.is_authenticated() class FormEndpoint(object): diff --git a/setup.py b/setup.py index 77cc91a..45aac0e 100644 --- a/setup.py +++ b/setup.py @@ -13,8 +13,11 @@ license='BSD', packages=find_packages(), zip_safe=False, - install_requires=['decorator',], - extras_require = { + install_requires=[ + 'decorator', + 'django-appconf==0.6', + ], + extras_require={ 'Tagging': ['taggit'] }, include_package_data=True, From f03a1e65609cf68f0bee65f82048c6ae5ffe9200 Mon Sep 17 00:00:00 2001 From: Cameron Stitt Date: Wed, 28 Aug 2013 17:06:15 -0700 Subject: [PATCH 03/62] Init the auth class --- ajax/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index 3eb16bd..385e34a 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -31,7 +31,7 @@ class ModelEndpoint(object): immutable_fields = [] # List of model fields that are not writable. - authentication = path_to_import(settings.AJAX_AUTHENTICATION) + authentication = path_to_import(settings.AJAX_AUTHENTICATION)() def __init__(self, application, model, method, **kwargs): self.application = application From eed04f777e4c9f7313668f56f3f2d500eb27e66f Mon Sep 17 00:00:00 2001 From: Cameron Stitt Date: Wed, 28 Aug 2013 17:20:08 -0700 Subject: [PATCH 04/62] Add the right parameters --- ajax/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index 385e34a..cba7cce 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -205,7 +205,7 @@ def authenticate(self, request, application, method): Most likely you will want to lock down who can edit and delete various models. To do this, just override this method in your child class. """ - return self.authentication.is_authenticated() + return self.authentication.is_authenticated(request, application, method) class FormEndpoint(object): From e567e9e4e9c33919395e538fdb2f01be494d498d Mon Sep 17 00:00:00 2001 From: Jack Carter Date: Sat, 18 Jan 2014 15:11:56 -0600 Subject: [PATCH 05/62] Remove reference to defaults module, which was removed in Django 1.6 --- ajax/urls.py | 2 +- tests/urls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ajax/urls.py b/ajax/urls.py index b7ef0bd..9a807c7 100644 --- a/ajax/urls.py +++ b/ajax/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls.defaults import * +from django.conf.urls import * from django.views.static import serve import os diff --git a/tests/urls.py b/tests/urls.py index fd6de3b..5117a13 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls.defaults import patterns, include, url +from django.conf.urls import patterns, include, url urlpatterns = patterns('', From ad673571984f60e457febc65b3db11bd156253a7 Mon Sep 17 00:00:00 2001 From: Sean Kelley Date: Sun, 23 Feb 2014 18:17:53 -0500 Subject: [PATCH 06/62] Try to use Python's built-in json module. --- ajax/exceptions.py | 6 +++++- ajax/middleware/DebugToolbar.py | 6 +++++- ajax/views.py | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ajax/exceptions.py b/ajax/exceptions.py index 4baa800..994b5d2 100644 --- a/ajax/exceptions.py +++ b/ajax/exceptions.py @@ -1,4 +1,8 @@ -from django.utils import simplejson as json +try: + import json +except ImportError: + from django.utils import simplejson as json + from django.utils.encoding import smart_str from django.http import HttpResponse, HttpResponseNotFound, \ HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseServerError, \ diff --git a/ajax/middleware/DebugToolbar.py b/ajax/middleware/DebugToolbar.py index 6147dd7..b29c033 100644 --- a/ajax/middleware/DebugToolbar.py +++ b/ajax/middleware/DebugToolbar.py @@ -1,5 +1,9 @@ +try: + import json +except ImportError: + from django.utils import simplejson as json + from debug_toolbar.middleware import DebugToolbarMiddleware, add_content_handler -from django.utils import simplejson as json from django.core.serializers.json import DjangoJSONEncoder diff --git a/ajax/views.py b/ajax/views.py index d8f0545..13905ae 100755 --- a/ajax/views.py +++ b/ajax/views.py @@ -1,6 +1,10 @@ +try: + import json +except ImportError: + from django.utils import simplejson as json + from django.conf import settings from django.http import HttpResponse -from django.utils import simplejson as json from django.utils.translation import ugettext as _ from django.utils.importlib import import_module from django.utils.log import getLogger From f460f432e0b1d1a2387eb59b823d2859d5a48937 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 19 Mar 2014 09:53:58 -0700 Subject: [PATCH 07/62] Deny list access unless it's specifically allowed. --- ajax/endpoints.py | 4 ++++ tests/example/endpoints.py | 7 ++++++- tests/example/tests.py | 10 ++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index 063d867..d32cbde 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -88,6 +88,9 @@ def list(self,request): items_per_page = min(max_items_per_page, requested_items_per_page) current_page = request.POST.get("current_page", 1) + if not self.can_list(request.user): + raise AJAXError(403, _("Access to this endpoint is forbidden")) + if hasattr(self, 'get_queryset'): objects = self.get_queryset(request.user) else: @@ -235,6 +238,7 @@ def _user_is_active_or_staff(self, user, record, **kwargs): can_create = _user_is_active_or_staff can_update = _user_is_active_or_staff can_delete = _user_is_active_or_staff + can_list = lambda *args, **kwargs: False def authenticate(self, request, application, method): """Authenticate the AJAX request. diff --git a/tests/example/endpoints.py b/tests/example/endpoints.py index b3de90a..9bcf3e7 100644 --- a/tests/example/endpoints.py +++ b/tests/example/endpoints.py @@ -1,7 +1,7 @@ from ajax import endpoint from ajax.decorators import login_required from ajax.endpoints import ModelEndpoint -from .models import Widget +from .models import Widget, Category @login_required @@ -13,5 +13,10 @@ def echo(request): class WidgetEndpoint(ModelEndpoint): model = Widget max_per_page = 100 + can_list = lambda *args, **kwargs: True + +class CategoryEndpoint(ModelEndpoint): + model = Category endpoint.register(Widget, WidgetEndpoint) +endpoint.register(Category, CategoryEndpoint) diff --git a/tests/example/tests.py b/tests/example/tests.py index 30d220b..299e73e 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -1,8 +1,9 @@ from django.test import TestCase from django.contrib.auth.models import User import json -from .models import Widget -from .endpoints import WidgetEndpoint +from ajax.exceptions import AJAXError +from .models import Widget, Category +from .endpoints import WidgetEndpoint, CategoryEndpoint class BaseTest(TestCase): @@ -87,11 +88,13 @@ def test_logged_out_user_fails(self): class MockRequest(object): def __init__(self, **kwargs): self.POST = kwargs + self.user = None class ModelEndpointTests(BaseTest): def setUp(self): self.list_endpoint = WidgetEndpoint('example', Widget, 'list') + self.category_endpoint = CategoryEndpoint('example', Category, 'list') def test_list_returns_all_items(self): results = self.list_endpoint.list(MockRequest()) @@ -102,6 +105,9 @@ def test_list_obeys_endpoint_pagination_amount(self): results = self.list_endpoint.list(MockRequest()) self.assertEqual(len(results), 1) + def test_list__ajaxerror_if_can_list_isnt_set(self): + self.assertRaises(AJAXError, self.category_endpoint.list, MockRequest()) + def test_out_of_range_returns_empty_list(self): results = self.list_endpoint.list(MockRequest(current_page=99)) self.assertEqual(len(results), 0) From b5e17d894106c1931deb0db9bec9794b6ae11ba0 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 19 Mar 2014 10:17:47 -0700 Subject: [PATCH 08/62] Claim the ajax package name since django-ajax is taken. Bump version to 0.3.0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 77cc91a..3923000 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,8 @@ setup( - name='django-ajax', - version='0.1.0', + name='ajax', + version='0.3.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From d0eeab4b37d2c29051b6807b0db3ba857b900f25 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 19 Mar 2014 10:31:21 -0700 Subject: [PATCH 09/62] All things that want to list, must specify a queryset to list from. Also has a backwards incompatible change to `get_queryset` method. --- ajax/endpoints.py | 12 ++++++------ tests/example/endpoints.py | 6 ++++++ tests/example/tests.py | 8 ++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index d32cbde..4575366 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -71,7 +71,10 @@ def tags(self, request): return encoder.encode(result) - def list(self,request): + def get_queryset(self, request): + return self.model.objects.none() + + def list(self, request): """ List objects of a model. By default will show page 1 with 20 objects on it. @@ -91,11 +94,8 @@ def list(self,request): if not self.can_list(request.user): raise AJAXError(403, _("Access to this endpoint is forbidden")) - if hasattr(self, 'get_queryset'): - objects = self.get_queryset(request.user) - else: - objects = self.model.objects.all() - + objects = self.get_queryset(request) + paginator = Paginator(objects, items_per_page) try: diff --git a/tests/example/endpoints.py b/tests/example/endpoints.py index 9bcf3e7..b7b4647 100644 --- a/tests/example/endpoints.py +++ b/tests/example/endpoints.py @@ -15,6 +15,12 @@ class WidgetEndpoint(ModelEndpoint): max_per_page = 100 can_list = lambda *args, **kwargs: True +class CategoryEndpoint(ModelEndpoint): + model = Category + + def get_queryset(self, request): + return Widget.objects.all() + class CategoryEndpoint(ModelEndpoint): model = Category diff --git a/tests/example/tests.py b/tests/example/tests.py index 299e73e..de81811 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -116,3 +116,11 @@ def test_request_doesnt_override_max_per_page(self): self.list_endpoint.max_per_page = 1 results = self.list_endpoint.list(MockRequest(items_per_page=2)) self.assertEqual(len(results), 1) + + def test_list_has_permission__default_empty(self): + Category.objects.create(title='test') + + ce = CategoryEndpoint('example', Category, 'list') + ce.can_list = lambda *args, **kwargs: True + + self.assertEqual(0, len(ce.list(MockRequest()))) From 8dc271cdd6d3a3eeff6b69ebb4958c83458864e9 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 19 Mar 2014 10:44:40 -0700 Subject: [PATCH 10/62] Fix tests after merge. --- tests/example/endpoints.py | 4 +--- tests/example/tests.py | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/example/endpoints.py b/tests/example/endpoints.py index b7b4647..f90153b 100644 --- a/tests/example/endpoints.py +++ b/tests/example/endpoints.py @@ -15,14 +15,12 @@ class WidgetEndpoint(ModelEndpoint): max_per_page = 100 can_list = lambda *args, **kwargs: True -class CategoryEndpoint(ModelEndpoint): - model = Category - def get_queryset(self, request): return Widget.objects.all() class CategoryEndpoint(ModelEndpoint): model = Category + endpoint.register(Widget, WidgetEndpoint) endpoint.register(Category, CategoryEndpoint) diff --git a/tests/example/tests.py b/tests/example/tests.py index de81811..0041999 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -120,7 +120,7 @@ def test_request_doesnt_override_max_per_page(self): def test_list_has_permission__default_empty(self): Category.objects.create(title='test') - ce = CategoryEndpoint('example', Category, 'list') - ce.can_list = lambda *args, **kwargs: True + self.category_endpoint.can_list = lambda *args, **kwargs: True - self.assertEqual(0, len(ce.list(MockRequest()))) + results = self.category_endpoint.list(MockRequest()) + self.assertEqual(0, len(results)) From d454ab145bfe616623e31087387531ede029ff9b Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 19 Mar 2014 10:56:56 -0700 Subject: [PATCH 11/62] Start to travis integration. --- .travis.yml | 9 +++++++++ ci.txt | 3 +++ requirements.txt | 2 ++ 3 files changed, 14 insertions(+) create mode 100644 .travis.yml create mode 100644 ci.txt create mode 100644 requirements.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e6a0f92 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: python +env: +- DJANGO_VERSION=1.4 +- DJANGO_VERSION=1.5 +python: +- '2.6' +- '2.7' +install: pip install -r ci.txt Django==$DJANGO_VERSION +script: make test diff --git a/ci.txt b/ci.txt new file mode 100644 index 0000000..be9e4c4 --- /dev/null +++ b/ci.txt @@ -0,0 +1,3 @@ +decorator +django-appconf==0.6 +django-jenkins diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..09e5c3b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +-r ./ci.txt +django==1.5 From cf66b17fe911e9deed1a35a0f797b3eaef6212c0 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 19 Mar 2014 13:09:46 -0700 Subject: [PATCH 12/62] Touch to make travis build. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e6a0f92..a0b9596 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,3 +7,4 @@ python: - '2.7' install: pip install -r ci.txt Django==$DJANGO_VERSION script: make test + From 7e28b71ea429c5f9f8e4609492905353da12dce5 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 19 Mar 2014 13:30:40 -0700 Subject: [PATCH 13/62] Install django-ajax into the env. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a0b9596..85f10ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ env: python: - '2.6' - '2.7' -install: pip install -r ci.txt Django==$DJANGO_VERSION +install: +- pip install -r ci.txt Django==$DJANGO_VERSION +- python setup.py install script: make test From 82519c63a6687b601052c20be8d1b1c494b3e09c Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 19 Mar 2014 17:40:59 -0700 Subject: [PATCH 14/62] Add test status badge. --- README.mkd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.mkd b/README.mkd index 36492dc..70cdf13 100644 --- a/README.mkd +++ b/README.mkd @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/joestump/django-ajax.png?branch=master)](https://travis-ci.org/joestump/django-ajax) + # Overview This package creates a minimal framework for creating AJAX endpoints of your own in Django without having to create all of the mappings, handling errors, building JSON, etc. From 93f95b147e901ce3df4b776a51fa8efc13737d9b Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 19 Mar 2014 18:01:28 -0700 Subject: [PATCH 15/62] no emails. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 85f10ed..66be9a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: python +notifications: + email: false env: - DJANGO_VERSION=1.4 - DJANGO_VERSION=1.5 From 5562e18f8c3c98fe3ee13e4403c7b85a99eba3cd Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Thu, 20 Mar 2014 11:50:43 -0700 Subject: [PATCH 16/62] get_queryset takes kwargs for future expansion. --- ajax/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index 4575366..fbe3945 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -71,7 +71,7 @@ def tags(self, request): return encoder.encode(result) - def get_queryset(self, request): + def get_queryset(self, request, **kwargs): return self.model.objects.none() def list(self, request): From e27a032391f0add1bfe6db4e55dfca9433910427 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Thu, 20 Mar 2014 12:04:19 -0700 Subject: [PATCH 17/62] Version bump for backwards incompatible list API change. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3923000..fe1e066 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ajax', - version='0.3.0', + version='1.0.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From ee2f742ce21d760845a97fa9a393e00a80606253 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Thu, 20 Mar 2014 12:10:05 -0700 Subject: [PATCH 18/62] patch bump for django 1.7a bug --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fe1e066..9553bc4 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ajax', - version='1.0.0', + version='1.0.1', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From 1476e42d083742826e07581543ffb1eaf9239db2 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Thu, 20 Mar 2014 12:10:46 -0700 Subject: [PATCH 19/62] release directive for makefile. --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 7237b13..6dd16d1 100644 --- a/Makefile +++ b/Makefile @@ -4,3 +4,6 @@ help: test: python tests/manage.py test example + +release: + python setup.py sdist upload From edd7af22eea87cb0d8e907f729c7ba3975f1435a Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Fri, 28 Mar 2014 11:07:35 -0700 Subject: [PATCH 20/62] Add support for total number of results returned Also adds support for adding information to the response wrapper. --- ajax/endpoints.py | 7 +++++-- ajax/views.py | 33 +++++++++++++++++++++++++++------ tests/example/tests.py | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index fbe3945..d52ee5c 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -8,6 +8,7 @@ from ajax.signals import ajax_created, ajax_deleted, ajax_updated from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.conf import settings +from ajax.views import EnvelopedResponse try: from taggit.utils import parse_tags @@ -106,8 +107,10 @@ def list(self, request): except EmptyPage: # If page is out of range (e.g. 9999), return empty list. page = EmptyPageResult() - - return [encoder.encode(record) for record in page.object_list] + + data = [encoder.encode(record) for record in page.object_list] + return EnvelopedResponse(data=data, metadata={'total': paginator.count}) + def _set_tags(self, request, record): tags = self._extract_tags(request) diff --git a/ajax/views.py b/ajax/views.py index 13905ae..d4bdfdb 100755 --- a/ajax/views.py +++ b/ajax/views.py @@ -17,6 +17,19 @@ logger = getLogger('django.request') +class EnvelopedResponse(object): + """ + Object used to contain metadata about the request that will be added to + the wrapping json structure (aka the envelope). + + :param: data - The object representation that you want to return + :param: metadata - dict of information which will be merged with the + envelope. + """ + def __init__(self, data, metadata): + self.data = data + self.metadata = metadata + @json_response def endpoint_loader(request, application, model, **kwargs): """Load an AJAX endpoint. @@ -64,10 +77,18 @@ def endpoint_loader(request, application, model, **kwargs): data = endpoint(request) if isinstance(data, HttpResponse): return data + + if isinstance(data, EnvelopedResponse): + envelope = data.metadata + payload = data.data else: - payload = { - 'success': True, - 'data': data, - } - return HttpResponse(json.dumps(payload, cls=DjangoJSONEncoder, - separators=(',', ':'))) + envelope = {} + payload = data + + envelope.update({ + 'success': True, + 'data': payload, + }) + + return HttpResponse(json.dumps(envelope, cls=DjangoJSONEncoder, + separators=(',', ':'))) diff --git a/tests/example/tests.py b/tests/example/tests.py index 0041999..bfec892 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -98,24 +98,24 @@ def setUp(self): def test_list_returns_all_items(self): results = self.list_endpoint.list(MockRequest()) - self.assertEqual(len(results), Widget.objects.count()) + self.assertEqual(len(results.data), Widget.objects.count()) def test_list_obeys_endpoint_pagination_amount(self): self.list_endpoint.max_per_page = 1 results = self.list_endpoint.list(MockRequest()) - self.assertEqual(len(results), 1) + self.assertEqual(len(results.data), 1) def test_list__ajaxerror_if_can_list_isnt_set(self): self.assertRaises(AJAXError, self.category_endpoint.list, MockRequest()) def test_out_of_range_returns_empty_list(self): results = self.list_endpoint.list(MockRequest(current_page=99)) - self.assertEqual(len(results), 0) + self.assertEqual(len(results.data), 0) def test_request_doesnt_override_max_per_page(self): self.list_endpoint.max_per_page = 1 results = self.list_endpoint.list(MockRequest(items_per_page=2)) - self.assertEqual(len(results), 1) + self.assertEqual(len(results.data), 1) def test_list_has_permission__default_empty(self): Category.objects.create(title='test') @@ -123,4 +123,29 @@ def test_list_has_permission__default_empty(self): self.category_endpoint.can_list = lambda *args, **kwargs: True results = self.category_endpoint.list(MockRequest()) - self.assertEqual(0, len(results)) + self.assertEqual(0, len(results.data)) + + def test_list_has_total(self): + self.category_endpoint.can_list = lambda *args, **kwargs: True + + results = self.list_endpoint.list(MockRequest()) + self.assertEqual(6, results.metadata['total']) + +class ModelEndpointPostTests(TestCase): + """ + Integration test for full urls->views->endpoint->encoder (and back) cycle. + """ + def setUp(self): + for title in ['first', 'second', 'third']: + Widget.objects.create(title=title) + u = User(email='test@example.org', username='test') + u.set_password('password') + u.save() + + def test_can_request_list_with_total(self): + self.client.login(username='test', password='password') + + resp = self.client.post('/ajax/example/widget/list.json') + content = json.loads(resp.content) + self.assertTrue('total' in content.keys()) + self.assertEquals(content['total'], 3) \ No newline at end of file From b0d6c4daa93ba9048c340eba0a05fe1144ef68c6 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Fri, 28 Mar 2014 12:00:03 -0700 Subject: [PATCH 21/62] Minor bump for length on list envelopes. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9553bc4..b890d11 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ajax', - version='1.0.1', + version='1.1.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From ed60413fceda1d7963d68a12ff2abc249526c3d6 Mon Sep 17 00:00:00 2001 From: Cameron Stitt Date: Thu, 10 Apr 2014 14:50:03 +1000 Subject: [PATCH 22/62] Merge upstream. Add Django 1.6 to test runner. --- .travis.yml | 14 ++++ Makefile | 3 + README.mkd | 40 +++++++++++ ajax/decorators.py | 13 ++++ ajax/endpoints.py | 72 +++++++++++++++++--- ajax/exceptions.py | 6 +- ajax/middleware/DebugToolbar.py | 6 +- ajax/views.py | 39 +++++++++-- ci.txt | 3 + requirements.txt | 2 + setup.py | 4 +- tests/example/endpoints.py | 18 ++++- tests/example/fixtures/categories.json | 30 +++++++++ tests/example/fixtures/widgets.json | 42 ++++++++++++ tests/example/models.py | 4 ++ tests/example/tests.py | 91 ++++++++++++++++++++++++-- tests/settings.py | 3 + 17 files changed, 364 insertions(+), 26 deletions(-) create mode 100644 .travis.yml create mode 100644 ci.txt create mode 100644 requirements.txt create mode 100644 tests/example/fixtures/categories.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..22c3508 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: python +notifications: + email: false +env: +- DJANGO_VERSION=1.4 +- DJANGO_VERSION=1.5 +- DJANGO_VERSION=1.6 +python: +- '2.6' +- '2.7' +install: +- pip install -r ci.txt Django==$DJANGO_VERSION +- python setup.py install +script: make test diff --git a/Makefile b/Makefile index 7237b13..6dd16d1 100644 --- a/Makefile +++ b/Makefile @@ -4,3 +4,6 @@ help: test: python tests/manage.py test example + +release: + python setup.py sdist upload diff --git a/README.mkd b/README.mkd index ec0ccdd..70cdf13 100644 --- a/README.mkd +++ b/README.mkd @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/joestump/django-ajax.png?branch=master)](https://travis-ci.org/joestump/django-ajax) + # Overview This package creates a minimal framework for creating AJAX endpoints of your own in Django without having to create all of the mappings, handling errors, building JSON, etc. @@ -16,6 +18,12 @@ pip install -e git://github.com/joestump/django-ajax#egg=django-ajax There are no models associated with this package so you don't need to worry about syncing your database or anything. +## Settings + +You can add the following settings to `settings.py` : + +* `AJAX_MAX_PER_PAGE` (optional: defaults to 100). Sets the maximum number of objects that can be returned per page using the built-in `list` method on a `ModelEndpoint` + # Usage You can use this package to create ad-hoc AJAX endpoints or by exposing your Django applications's models via AJAX. @@ -91,6 +99,7 @@ AJAX also offers a class, called `ModelEndpoint`, that takes a Django model and You can then send a POST to: * `/ajax/my_app/category.json` to create a new `Category`. +* `/ajax/my_app/category/list.json` to get a list of Category. Additionally, you can POST `items_per_page` (default is 20) and `current_page` (default is 1). * `/ajax/my_app/category/{pk}/update.json` to update a `Category`. `pk` must be present in the path for `update`, `delete`, and `get`. **NOTE:** This package assumes that the `pk` argument is an integer of some sort. If you've mangled your `pk` fields in weird ways, this likely will not work as expected. * `/ajax/my_app/category/{pk}/get.json` to get the `Category` as specified as `pk`. * `/ajax/my_app/category/{pk}/delete.json` to delete the `Category` as specified by `pk`. @@ -161,6 +170,8 @@ There are a number of security features inherent in the framework along with way ## Decorators +#### ajax.decorators.login_required + The AJAX package offers a familiar decorator in `ajax.decorators` called `login_required`. It works in the same way that the Django decorator does and handles throwing a proper `AJAXError` if the user isn't logged in. from ajax.decorators import login_required @@ -171,6 +182,17 @@ The AJAX package offers a familiar decorator in `ajax.decorators` called `login_ 'Hello, %s!' % request.user.username } +#### ajax.decorators.allowed_methods +You can limit methods that are allowed on your `ModelEndpoint` by decorating your overriding ModelEndpoint.authenticate() method with the `allowed_methods` decorator. If a user tries to access a disallowed method, then they will receive an `AJAXError`. + + from ajax.decorators import allowed_methods + + class CategoryEndpoint(ajax.endpoints.ModelEndpoint): + + @allowed_methods('get','update','list') + def authenticate(self,request): + ... + ## ModelEndpoint The `ModelEndpoint` class offers a number of methods that you can override to add more advanced security over your model-based enpoints. You can override these in your model-based endpoints to control who is able to access each model and in what manner. @@ -195,6 +217,24 @@ Returns `True` if the `user` can fetch the given `record`. This method is ran before any other security-related operations. This method allows you to control overall authentication-related access prior to any of the `can_*` methods being ran and before any model operations are executed. It is ran regardless of the operation being attempted. +## Encoders + +AJAX ships with a few helpful encoders. You can specify the model fields you would like to include or exclude using the `IncludeEncoder` and `ExcludeEncoder`, respectively. + +To only include certain fields: + + from ajax.encoders import encoder, IncludeEncoder + from my_app.models import Category + + encoder.register(Category, IncludeEncoder(include=['foo', 'bar'])) + +To exclude a field: + + from ajax.encoders import encoder, IncludeEncoder + from my_app.models import Category + + encoder.register(Category, ExcludeEncoder(exclude=['foo'])) + # Todo 1. Integrate [Django's CSRF token support](http://docs.djangoproject.com/en/dev/ref/contrib/csrf/). diff --git a/ajax/decorators.py b/ajax/decorators.py index 3ad91b9..9546e76 100644 --- a/ajax/decorators.py +++ b/ajax/decorators.py @@ -4,6 +4,8 @@ from django.conf import settings from decorator import decorator from ajax.exceptions import AJAXError, PrimaryKeyMissing +from functools import wraps +from django.utils.decorators import available_attrs logger = getLogger('django.request') @@ -25,6 +27,17 @@ def require_pk(func, *args, **kwargs): return func(*args, **kwargs) +def allowed_methods(*args,**kwargs): + request_method_list = args + def decorator(func): + @wraps(func, assigned=available_attrs(func)) + def inner(request, *args, **kwargs): + if request.method not in request_method_list: + raise AJAXError(403, _('Access denied.')) + return func(request, *args, **kwargs) + return inner + return decorator + @decorator def json_response(f, *args, **kwargs): """Wrap a view in JSON. diff --git a/ajax/endpoints.py b/ajax/endpoints.py index cba7cce..fff1370 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -1,19 +1,16 @@ -from django.core import serializers from django.core.exceptions import ValidationError +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.db import models -from django.utils import simplejson as json from django.utils.encoding import smart_str from django.utils.translation import ugettext_lazy as _ -from django.db.models.fields import FieldDoesNotExist from ajax.compat import path_to_import from ajax.conf import settings from ajax.decorators import require_pk -from ajax.exceptions import AJAXError, AlreadyRegistered, NotRegistered, \ - PrimaryKeyMissing +from ajax.exceptions import AJAXError, AlreadyRegistered, NotRegistered from ajax.encoders import encoder from ajax.signals import ajax_created, ajax_deleted, ajax_updated - +from ajax.views import EnvelopedResponse try: from taggit.utils import parse_tags @@ -22,6 +19,11 @@ def parse_tags(tagstring): raise AJAXError(500, 'Taggit required: http://bit.ly/RE0dr9') +class EmptyPageResult(object): + def __init__(self): + self.object_list = [] + + class ModelEndpoint(object): _value_map = { 'false': False, @@ -74,6 +76,51 @@ def tags(self, request): return encoder.encode(result) + def get_queryset(self, request, **kwargs): + return self.model.objects.none() + + def list(self, request): + """ + List objects of a model. By default will show page 1 with 20 objects on it. + + **Usage**:: + + params = {"items_per_page":10,"page":2} //all params are optional + $.post("/ajax/{app}/{model}/list.json"),params) + + """ + + max_items_per_page = getattr(self, 'max_per_page', + getattr(settings, 'AJAX_MAX_PER_PAGE', 100)) + requested_items_per_page = request.POST.get("items_per_page", 20) + items_per_page = min(max_items_per_page, requested_items_per_page) + current_page = request.POST.get("current_page", 1) + + if not self.can_list(request.user): + raise AJAXError(403, _("Access to this endpoint is forbidden")) + + objects = self.get_queryset(request) + + paginator = Paginator(objects, items_per_page) + + try: + page = paginator.page(current_page) + except PageNotAnInteger: + # If page is not an integer, deliver first page. + page = paginator.page(1) + except EmptyPage: + # If page is out of range (e.g. 9999), return empty list. + page = EmptyPageResult() + + data = [encoder.encode(record) for record in page.object_list] + return EnvelopedResponse(data=data, metadata={'total': paginator.count}) + + + def _set_tags(self, request, record): + tags = self._extract_tags(request) + if tags: + record.tags.set(*tags) + def _save(self, record): try: record.full_clean() @@ -157,12 +204,16 @@ def _extract_data(self, request): continue # Ignore immutable fields silently. if field in self.fields: - f = self.model._meta.get_field(field) + field_obj = self.model._meta.get_field(field) val = self._extract_value(val) - if val and isinstance(f, models.ForeignKey): - data[smart_str(field)] = f.rel.to.objects.get(pk=val) + if isinstance(field_obj, models.ForeignKey): + if field_obj.null and not val: + clean_value = None + else: + clean_value = field_obj.rel.to.objects.get(pk=val) else: - data[smart_str(field)] = val + clean_value = val + data[smart_str(field)] = clean_value return data @@ -194,6 +245,7 @@ def _user_is_active_or_staff(self, user, record, **kwargs): can_create = _user_is_active_or_staff can_update = _user_is_active_or_staff can_delete = _user_is_active_or_staff + can_list = lambda *args, **kwargs: False def authenticate(self, request, application, method): """Authenticate the AJAX request. diff --git a/ajax/exceptions.py b/ajax/exceptions.py index 4baa800..994b5d2 100644 --- a/ajax/exceptions.py +++ b/ajax/exceptions.py @@ -1,4 +1,8 @@ -from django.utils import simplejson as json +try: + import json +except ImportError: + from django.utils import simplejson as json + from django.utils.encoding import smart_str from django.http import HttpResponse, HttpResponseNotFound, \ HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseServerError, \ diff --git a/ajax/middleware/DebugToolbar.py b/ajax/middleware/DebugToolbar.py index 6147dd7..b29c033 100644 --- a/ajax/middleware/DebugToolbar.py +++ b/ajax/middleware/DebugToolbar.py @@ -1,5 +1,9 @@ +try: + import json +except ImportError: + from django.utils import simplejson as json + from debug_toolbar.middleware import DebugToolbarMiddleware, add_content_handler -from django.utils import simplejson as json from django.core.serializers.json import DjangoJSONEncoder diff --git a/ajax/views.py b/ajax/views.py index d8f0545..d4bdfdb 100755 --- a/ajax/views.py +++ b/ajax/views.py @@ -1,6 +1,10 @@ +try: + import json +except ImportError: + from django.utils import simplejson as json + from django.conf import settings from django.http import HttpResponse -from django.utils import simplejson as json from django.utils.translation import ugettext as _ from django.utils.importlib import import_module from django.utils.log import getLogger @@ -13,6 +17,19 @@ logger = getLogger('django.request') +class EnvelopedResponse(object): + """ + Object used to contain metadata about the request that will be added to + the wrapping json structure (aka the envelope). + + :param: data - The object representation that you want to return + :param: metadata - dict of information which will be merged with the + envelope. + """ + def __init__(self, data, metadata): + self.data = data + self.metadata = metadata + @json_response def endpoint_loader(request, application, model, **kwargs): """Load an AJAX endpoint. @@ -60,10 +77,18 @@ def endpoint_loader(request, application, model, **kwargs): data = endpoint(request) if isinstance(data, HttpResponse): return data + + if isinstance(data, EnvelopedResponse): + envelope = data.metadata + payload = data.data else: - payload = { - 'success': True, - 'data': data, - } - return HttpResponse(json.dumps(payload, cls=DjangoJSONEncoder, - separators=(',', ':'))) + envelope = {} + payload = data + + envelope.update({ + 'success': True, + 'data': payload, + }) + + return HttpResponse(json.dumps(envelope, cls=DjangoJSONEncoder, + separators=(',', ':'))) diff --git a/ci.txt b/ci.txt new file mode 100644 index 0000000..be9e4c4 --- /dev/null +++ b/ci.txt @@ -0,0 +1,3 @@ +decorator +django-appconf==0.6 +django-jenkins diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..09e5c3b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +-r ./ci.txt +django==1.5 diff --git a/setup.py b/setup.py index 45aac0e..3bf36d2 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,8 @@ setup( - name='django-ajax', - version='0.1.0', + name='ajax', + version='1.1.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', diff --git a/tests/example/endpoints.py b/tests/example/endpoints.py index b5295be..f90153b 100644 --- a/tests/example/endpoints.py +++ b/tests/example/endpoints.py @@ -1,10 +1,26 @@ from ajax import endpoint from ajax.decorators import login_required from ajax.endpoints import ModelEndpoint -from ajax.exceptions import AJAXError +from .models import Widget, Category @login_required def echo(request): """For testing purposes only.""" return request.POST + + +class WidgetEndpoint(ModelEndpoint): + model = Widget + max_per_page = 100 + can_list = lambda *args, **kwargs: True + + def get_queryset(self, request): + return Widget.objects.all() + +class CategoryEndpoint(ModelEndpoint): + model = Category + + +endpoint.register(Widget, WidgetEndpoint) +endpoint.register(Category, CategoryEndpoint) diff --git a/tests/example/fixtures/categories.json b/tests/example/fixtures/categories.json new file mode 100644 index 0000000..29c6d25 --- /dev/null +++ b/tests/example/fixtures/categories.json @@ -0,0 +1,30 @@ +[ + { + "pk": 1, + "model": "example.category", + "fields": { + "title": "Proin mollis, massa quis pulvinar viverra" + } + }, + { + "pk": 2, + "model": "example.category", + "fields": { + "title": "Vestibulum suscipit pulvinar purus" + } + }, + { + "pk": 3, + "model": "example.category", + "fields": { + "title": "Morbi sed bibendum dui" + } + }, + { + "pk": 4, + "model": "example.category", + "fields": { + "title": "Aenean euismod cursus posuere" + } + } +] \ No newline at end of file diff --git a/tests/example/fixtures/widgets.json b/tests/example/fixtures/widgets.json index f0adf43..43a392b 100644 --- a/tests/example/fixtures/widgets.json +++ b/tests/example/fixtures/widgets.json @@ -3,6 +3,7 @@ "pk": 1, "model": "example.widget", "fields": { + "category": null, "active": false, "description": null, "title": "Iorem lipsum color bit amit" @@ -12,9 +13,50 @@ "pk": 2, "model": "example.widget", "fields": { + "category": null, "active": true, "description": null, "title": "Lorem ipsum dolor sit amet" } + }, + { + "pk": 3, + "model": "example.widget", + "fields": { + "category": 1, + "active": true, + "description": null, + "title": "Sorem ipsum dolor lit amet" + } + }, + { + "pk": 4, + "model": "example.widget", + "fields": { + "category": 2, + "active": true, + "description": null, + "title": "Dorem ipsum solor bit amet" + } + }, + { + "pk": 5, + "model": "example.widget", + "fields": { + "category": 3, + "active": true, + "description": null, + "title": "Fusce in mattis elit" + } + }, + { + "pk": 6, + "model": "example.widget", + "fields": { + "category": 4, + "active": true, + "description": null, + "title": "Nulla quis turpis augue" + } } ] \ No newline at end of file diff --git a/tests/example/models.py b/tests/example/models.py index 472e8a8..28f2a8d 100644 --- a/tests/example/models.py +++ b/tests/example/models.py @@ -1,6 +1,10 @@ from django.db import models +class Category(models.Model): + title = models.CharField(max_length=100) + class Widget(models.Model): + category = models.ForeignKey(Category, null=True, blank=True) title = models.CharField(max_length=100) description = models.CharField(max_length=200, null=True, blank=True) active = models.BooleanField() diff --git a/tests/example/tests.py b/tests/example/tests.py index 8a1cc2c..bfec892 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -1,11 +1,13 @@ from django.test import TestCase from django.contrib.auth.models import User -from django.utils import simplejson as json -from example.models import Widget +import json +from ajax.exceptions import AJAXError +from .models import Widget, Category +from .endpoints import WidgetEndpoint, CategoryEndpoint class BaseTest(TestCase): - fixtures = ['users.json', 'widgets.json'] + fixtures = ['users.json', 'categories.json', 'widgets.json'] def setUp(self): self.login('jstump') @@ -35,7 +37,8 @@ def post(self, uri, data={}, debug=False, status_code=200): self.assertEquals(status_code, response.status_code) - return (response, json.loads(response.content)) + return response, json.loads(response.content) + class EncodeTests(BaseTest): def test_encode(self): @@ -61,8 +64,88 @@ def test_echo(self): self.assertEquals('Joe Stump', content['data']['name']) self.assertEquals('31', content['data']['age']) + def test_empty_foreign_key(self): + """Test that nullable ForeignKey fields can be set to null""" + resp, content = self.post('/ajax/example/widget/3/update.json', + {'category': ''}) + self.assertEquals(None, content['data']['category']) + self.assertEquals(None, Widget.objects.get(pk=3).category) + + def test_false_foreign_key(self): + """Test that nullable ForeignKey fields can be set to null by setting it to false""" + resp, content = self.post('/ajax/example/widget/6/update.json', + {'category': False}) + self.assertEquals(None, content['data']['category']) + self.assertEquals(None, Widget.objects.get(pk=6).category) + def test_logged_out_user_fails(self): """Make sure @login_required rejects requests to echo.""" self.client.logout() resp, content = self.post('/ajax/example/echo.json', {}, status_code=403) + + +class MockRequest(object): + def __init__(self, **kwargs): + self.POST = kwargs + self.user = None + + +class ModelEndpointTests(BaseTest): + def setUp(self): + self.list_endpoint = WidgetEndpoint('example', Widget, 'list') + self.category_endpoint = CategoryEndpoint('example', Category, 'list') + + def test_list_returns_all_items(self): + results = self.list_endpoint.list(MockRequest()) + self.assertEqual(len(results.data), Widget.objects.count()) + + def test_list_obeys_endpoint_pagination_amount(self): + self.list_endpoint.max_per_page = 1 + results = self.list_endpoint.list(MockRequest()) + self.assertEqual(len(results.data), 1) + + def test_list__ajaxerror_if_can_list_isnt_set(self): + self.assertRaises(AJAXError, self.category_endpoint.list, MockRequest()) + + def test_out_of_range_returns_empty_list(self): + results = self.list_endpoint.list(MockRequest(current_page=99)) + self.assertEqual(len(results.data), 0) + + def test_request_doesnt_override_max_per_page(self): + self.list_endpoint.max_per_page = 1 + results = self.list_endpoint.list(MockRequest(items_per_page=2)) + self.assertEqual(len(results.data), 1) + + def test_list_has_permission__default_empty(self): + Category.objects.create(title='test') + + self.category_endpoint.can_list = lambda *args, **kwargs: True + + results = self.category_endpoint.list(MockRequest()) + self.assertEqual(0, len(results.data)) + + def test_list_has_total(self): + self.category_endpoint.can_list = lambda *args, **kwargs: True + + results = self.list_endpoint.list(MockRequest()) + self.assertEqual(6, results.metadata['total']) + +class ModelEndpointPostTests(TestCase): + """ + Integration test for full urls->views->endpoint->encoder (and back) cycle. + """ + def setUp(self): + for title in ['first', 'second', 'third']: + Widget.objects.create(title=title) + u = User(email='test@example.org', username='test') + u.set_password('password') + u.save() + + def test_can_request_list_with_total(self): + self.client.login(username='test', password='password') + + resp = self.client.post('/ajax/example/widget/list.json') + content = json.loads(resp.content) + self.assertTrue('total' in content.keys()) + self.assertEquals(content['total'], 3) \ No newline at end of file diff --git a/tests/settings.py b/tests/settings.py index 90e2412..f67de08 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -165,3 +165,6 @@ # The test runner for the Jenkins command. JENKINS_TEST_RUNNER = 'django_jenkins.runner.CITestSuiteRunner' + +# django-ajax specific settings +MAX_PER_PAGE = 20 From 342857263b9d38330fd7b4f979d879777e334a59 Mon Sep 17 00:00:00 2001 From: Jack Carter Date: Sun, 11 May 2014 17:17:23 -0500 Subject: [PATCH 23/62] Add Django 1.6 to Travis tests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 66be9a9..6c3e53e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ notifications: env: - DJANGO_VERSION=1.4 - DJANGO_VERSION=1.5 +- DJANGO_VERSION=1.6 python: - '2.6' - '2.7' From 3cd42a7b7a11c19c13458897989adcdda0f04d8b Mon Sep 17 00:00:00 2001 From: Jack Carter Date: Sun, 11 May 2014 17:43:28 -0500 Subject: [PATCH 24/62] Update manage.py to Django-1.6 standards Remove reference to deprecated 'execute_manager' https://docs.djangoproject.com/en/1.6/releases/1.4/#updated-default-project-layout-and-manage-py --- tests/manage.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/manage.py b/tests/manage.py index 3e4eedc..00411b1 100755 --- a/tests/manage.py +++ b/tests/manage.py @@ -1,14 +1,9 @@ #!/usr/bin/env python -from django.core.management import execute_manager -import imp -try: - imp.find_module('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" % __file__) - sys.exit(1) - -import settings +import os, sys if __name__ == "__main__": - execute_manager(settings) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) \ No newline at end of file From 44a4afe3c1033fea1b042f7ae3157b0fc6952ef3 Mon Sep 17 00:00:00 2001 From: Jack Carter Date: Sun, 11 May 2014 19:32:53 -0500 Subject: [PATCH 25/62] Fix ModelEndpointPostTests setup -- non-nullable field was not specified --- tests/example/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/example/tests.py b/tests/example/tests.py index bfec892..ac8375b 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -137,7 +137,7 @@ class ModelEndpointPostTests(TestCase): """ def setUp(self): for title in ['first', 'second', 'third']: - Widget.objects.create(title=title) + Widget.objects.create(title=title, active=True) u = User(email='test@example.org', username='test') u.set_password('password') u.save() From e2db64ccf4c6e0cba79ff8d64b1768ce086d4eea Mon Sep 17 00:00:00 2001 From: Cameron Stitt Date: Thu, 5 Jun 2014 14:46:13 +1000 Subject: [PATCH 26/62] Support for django 1.6 --- ajax/urls.py | 6 +++--- tests/example/models.py | 2 +- tests/manage.py | 17 ++++++----------- tests/urls.py | 2 +- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/ajax/urls.py b/ajax/urls.py index b7ef0bd..af4289c 100644 --- a/ajax/urls.py +++ b/ajax/urls.py @@ -1,12 +1,12 @@ -from django.conf.urls.defaults import * +from django.conf.urls import * from django.views.static import serve import os JAVASCRIPT_PATH = "%s/js" % os.path.dirname(__file__) urlpatterns = patterns('ajax.views', - (r'^(?P\w+)/(?P\w+).json', 'endpoint_loader'), - (r'^(?P\w+)/(?P\w+)/(?P\w+).json', 'endpoint_loader'), + (r'^(?P\w+)/(?P\w+).json', 'endpoint_loader'), + (r'^(?P\w+)/(?P\w+)/(?P\w+).json', 'endpoint_loader'), (r'^(?P\w+)/(?P\w+)/(?P\d+)/(?P\w+)/?(?P(add|remove|set|clear|similar))?.json$', 'endpoint_loader'), (r'^js/(?P.*)$', serve, {'document_root': JAVASCRIPT_PATH}), diff --git a/tests/example/models.py b/tests/example/models.py index 28f2a8d..ff1414c 100644 --- a/tests/example/models.py +++ b/tests/example/models.py @@ -7,4 +7,4 @@ class Widget(models.Model): category = models.ForeignKey(Category, null=True, blank=True) title = models.CharField(max_length=100) description = models.CharField(max_length=200, null=True, blank=True) - active = models.BooleanField() + active = models.BooleanField(default=True) diff --git a/tests/manage.py b/tests/manage.py index 3e4eedc..c632a8a 100755 --- a/tests/manage.py +++ b/tests/manage.py @@ -1,14 +1,9 @@ #!/usr/bin/env python -from django.core.management import execute_manager -import imp -try: - imp.find_module('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" % __file__) - sys.exit(1) - -import settings +import os, sys if __name__ == "__main__": - execute_manager(settings) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/tests/urls.py b/tests/urls.py index fd6de3b..5117a13 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls.defaults import patterns, include, url +from django.conf.urls import patterns, include, url urlpatterns = patterns('', From 01fb01f104f3e998afa6d618716f94bf4e9361d1 Mon Sep 17 00:00:00 2001 From: Nabil Date: Sun, 21 Sep 2014 19:28:52 -0400 Subject: [PATCH 27/62] Updated README.mkd Fixed minor spelling error --- README.mkd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.mkd b/README.mkd index 70cdf13..172e1c0 100644 --- a/README.mkd +++ b/README.mkd @@ -108,7 +108,7 @@ You can then send a POST to: ### Adding Ad-hoc endpoints to ModelEndpoints -You can also add you own custom methods to a ModelEndpoint. Adhoc methods in a ModelEndpoint observe the same rules as the get(), update() and delete() methods - with the noticable exception that self.pk _may_ not be set. +You can also add you own custom methods to a ModelEndpoint. Adhoc methods in a ModelEndpoint observe the same rules as the get(), update() and delete() methods - with the noticeable exception that self.pk _may_ not be set. For example, you could add a method called `about` that will display some info about the Model or Record (just used for illustration: not actually a good idea in real life): From 414ef944ef183c5c2f2847d87af49d6cef88f873 Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Thu, 16 Oct 2014 13:50:55 -0700 Subject: [PATCH 28/62] Don't save record if nothing has changed. --- ajax/endpoints.py | 16 ++++++++++++---- requirements.txt | 1 + tests/example/tests.py | 24 ++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index fff1370..5e5fa62 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -134,11 +134,19 @@ def _save(self, record): def update(self, request): record = self._get_record() modified = self._get_record() - for key, val in self._extract_data(request).iteritems(): - setattr(modified, key, val) + if self.can_update(request.user, record, modified=modified): - self._save(modified) + update_record = False + for key, val in self._extract_data(request).iteritems(): + + # Only setattr and save the model when a change has happened. + if val != getattr(record, key): + setattr(modified, key, val) + update_record = True + + if update_record: + self._save(modified) try: tags = self._extract_tags(request) @@ -212,7 +220,7 @@ def _extract_data(self, request): else: clean_value = field_obj.rel.to.objects.get(pk=val) else: - clean_value = val + clean_value = self.model._meta.get_field(field).to_python(val) data[smart_str(field)] = clean_value return data diff --git a/requirements.txt b/requirements.txt index 09e5c3b..b4dd8cc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -r ./ci.txt django==1.5 +mock diff --git a/tests/example/tests.py b/tests/example/tests.py index ac8375b..cbc5613 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -1,7 +1,13 @@ +import json + +import mock + from django.test import TestCase from django.contrib.auth.models import User -import json + +from ajax.endpoints import ModelEndpoint from ajax.exceptions import AJAXError + from .models import Widget, Category from .endpoints import WidgetEndpoint, CategoryEndpoint @@ -54,7 +60,7 @@ def test_encode(self): widget = Widget.objects.get(pk=encoded['pk']) for k in ('title','active','description'): self.assertEquals(encoded[k],getattr(widget,k)) - + class EndpointTests(BaseTest): def test_echo(self): @@ -84,6 +90,20 @@ def test_logged_out_user_fails(self): resp, content = self.post('/ajax/example/echo.json', {}, status_code=403) + def test_has_changes_does_save(self): + """Test that updating to a new value calls save.""" + with mock.patch.object(ModelEndpoint, '_save') as mock_save: + resp, content = self.post('/ajax/example/widget/6/update.json', + {'active': False}) + self.assertTrue(mock_save.called) + + def test_no_changes_doesnt_save(self): + """Test that updating to an existing value doesnt call save.""" + with mock.patch.object(ModelEndpoint, '_save') as mock_save: + resp, content = self.post('/ajax/example/widget/6/update.json', + {'active': True}) + self.assertFalse(mock_save.called) + class MockRequest(object): def __init__(self, **kwargs): From 5d2136fe61f962cf68ea7b61a051c7132377a947 Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Thu, 16 Oct 2014 14:22:45 -0700 Subject: [PATCH 29/62] Move the can_update check back to original spot --- ajax/endpoints.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index 5e5fa62..3dbac60 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -135,15 +135,15 @@ def update(self, request): record = self._get_record() modified = self._get_record() - if self.can_update(request.user, record, modified=modified): - - update_record = False - for key, val in self._extract_data(request).iteritems(): + update_record = False + for key, val in self._extract_data(request).iteritems(): - # Only setattr and save the model when a change has happened. - if val != getattr(record, key): - setattr(modified, key, val) - update_record = True + # Only setattr and save the model when a change has happened. + if val != getattr(record, key): + setattr(modified, key, val) + update_record = True + + if self.can_update(request.user, record, modified=modified): if update_record: self._save(modified) From e37073634a73aff619e6bfe030cf436627c8ba66 Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Thu, 16 Oct 2014 14:26:16 -0700 Subject: [PATCH 30/62] Version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3bf36d2..6f1dca3 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ajax', - version='1.1.0', + version='1.1.1', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From a2d6bcb3694e90b19656c98e84f4f4b2e4fc2e0c Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Thu, 16 Oct 2014 14:56:46 -0700 Subject: [PATCH 31/62] Use available var --- ajax/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index 3dbac60..cb05cb4 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -220,7 +220,7 @@ def _extract_data(self, request): else: clean_value = field_obj.rel.to.objects.get(pk=val) else: - clean_value = self.model._meta.get_field(field).to_python(val) + clean_value = field_obj.to_python(val) data[smart_str(field)] = clean_value return data From 8cf1fbf11abbf575b5c435ee07b6005cb0a77348 Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Thu, 16 Oct 2014 15:36:29 -0700 Subject: [PATCH 32/62] Patch make test to work locally --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6dd16d1..e550700 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ help: @echo " tests to make a unit test run" test: - python tests/manage.py test example + PYTHONPATH=${PWD}:${PYTHONPATH} python tests/manage.py test example -release: +release: python setup.py sdist upload From 74a2c30a3149b3eb757a456e18ecc5e000098968 Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Thu, 16 Oct 2014 15:36:53 -0700 Subject: [PATCH 33/62] DeprecationWarning fix --- tests/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/settings.py b/tests/settings.py index f67de08..a4badb9 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -141,6 +141,7 @@ 'handlers': { 'mail_admins': { 'level': 'ERROR', + 'filters': [], 'class': 'django.utils.log.AdminEmailHandler' } }, From 02763dc901b2654959978a07f0c82a9dd364391e Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Thu, 16 Oct 2014 17:23:09 -0700 Subject: [PATCH 34/62] Makefile description --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e550700..48c8b76 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ help: @echo "Please use \`make ' where is one of" - @echo " tests to make a unit test run" + @echo " test to make unit tests run" test: PYTHONPATH=${PWD}:${PYTHONPATH} python tests/manage.py test example From 34d33a4ef1f8d0bc0a816f417e62b9acf51987bc Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Thu, 16 Oct 2014 17:30:37 -0700 Subject: [PATCH 35/62] 1.2.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6f1dca3..f3f89e7 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ajax', - version='1.1.1', + version='1.2.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From 56b48fec4c99abea094393037f5d8c37392e0cba Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Tue, 11 Nov 2014 15:47:33 -0500 Subject: [PATCH 36/62] Send payload along with ajax_deleted signal --- ajax/endpoints.py | 5 +++-- tests/example/tests.py | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ajax/endpoints.py b/ajax/endpoints.py index cb05cb4..87727ec 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -168,9 +168,10 @@ def update(self, request): def delete(self, request): record = self._get_record() if self.can_delete(request.user, record): + payload = {'pk': int(self.pk)} record.delete() - ajax_deleted.send(sender=record.__class__, instance=record) - return {'pk': int(self.pk)} + ajax_deleted.send(sender=record.__class__, instance=record, payload=payload) + return payload else: raise AJAXError(403, _("Access to endpoint is forbidden")) diff --git a/tests/example/tests.py b/tests/example/tests.py index cbc5613..cd6fc2c 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -7,6 +7,7 @@ from ajax.endpoints import ModelEndpoint from ajax.exceptions import AJAXError +from ajax.signals import ajax_deleted from .models import Widget, Category from .endpoints import WidgetEndpoint, CategoryEndpoint @@ -151,6 +152,7 @@ def test_list_has_total(self): results = self.list_endpoint.list(MockRequest()) self.assertEqual(6, results.metadata['total']) + class ModelEndpointPostTests(TestCase): """ Integration test for full urls->views->endpoint->encoder (and back) cycle. @@ -168,4 +170,16 @@ def test_can_request_list_with_total(self): resp = self.client.post('/ajax/example/widget/list.json') content = json.loads(resp.content) self.assertTrue('total' in content.keys()) - self.assertEquals(content['total'], 3) \ No newline at end of file + self.assertEquals(content['total'], 3) + + def test_delete(self): + widget = Widget.objects.all()[0] + mocked_ajax_delete_signal = mock.Mock() + ajax_deleted.connect(mocked_ajax_delete_signal) + + self.client.login(username='test', password='password') + + self.client.post('/ajax/example/widget/%s/delete.json' % widget.pk) + + self.assertTrue(mocked_ajax_delete_signal.called) + self.assertEqual(widget.pk, mocked_ajax_delete_signal.call_args[1]['payload']['pk']) From db7d1cf974040167abfe48fde76330bd6b9aa0a8 Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Tue, 11 Nov 2014 15:47:59 -0500 Subject: [PATCH 37/62] Minor version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f3f89e7..33f339f 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ajax', - version='1.2.0', + version='1.3.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From 67e2eec0535500181f460beaa8ec5d802daefcde Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Tue, 11 Nov 2014 17:05:56 -0500 Subject: [PATCH 38/62] Fix for Django 1.4 tests --- tests/example/tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/example/tests.py b/tests/example/tests.py index cd6fc2c..bf4dcca 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -174,7 +174,9 @@ def test_can_request_list_with_total(self): def test_delete(self): widget = Widget.objects.all()[0] - mocked_ajax_delete_signal = mock.Mock() + + # spec attr required for mocking signal in Django 1.4. + mocked_ajax_delete_signal = mock.Mock(spec=lambda: None) ajax_deleted.connect(mocked_ajax_delete_signal) self.client.login(username='test', password='password') From 01b486f207d0448d51910332fd5b022ee72a5a7e Mon Sep 17 00:00:00 2001 From: Nicholas Serra Date: Thu, 4 Dec 2014 13:31:57 -0500 Subject: [PATCH 39/62] Major version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 33f339f..a9a81fe 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ajax', - version='1.3.0', + version='2.0.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From 9f7b7461985fe7014e8d68abd7aa88b5da0df6c7 Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 17:32:27 -0700 Subject: [PATCH 40/62] Improve gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ba1b530..71058aa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ *.egg *.egg-info *.sqlite3 +env/ +py3env/ +.idea/ From 45e80772b33de92c47de8cfe0cbc24b3ded6777a Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 18:13:57 -0700 Subject: [PATCH 41/62] Bump reqs to support django 1.7/1.8 --- ci.txt | 3 ++- requirements.txt | 1 - setup.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ci.txt b/ci.txt index be9e4c4..200925f 100644 --- a/ci.txt +++ b/ci.txt @@ -1,3 +1,4 @@ decorator -django-appconf==0.6 +django-appconf~=1.0.0 django-jenkins +django>=1.4.*,<1.9,!=1.5.*,!=1.6.* \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b4dd8cc..ed077da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ -r ./ci.txt -django==1.5 mock diff --git a/setup.py b/setup.py index a9a81fe..c80a79a 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from setuptools import setup, find_packages @@ -15,7 +16,7 @@ zip_safe=False, install_requires=[ 'decorator', - 'django-appconf==0.6', + 'django-appconf==1.0.*', ], extras_require={ 'Tagging': ['taggit'] From 54ad49d87526b2c79a4e9299141d6891e9e872d5 Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 18:14:49 -0700 Subject: [PATCH 42/62] Update compat imports --- ajax/compat.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ajax/compat.py b/ajax/compat.py index af729ca..992da11 100644 --- a/ajax/compat.py +++ b/ajax/compat.py @@ -1,8 +1,10 @@ +from __future__ import absolute_import import django -if django.VERSION >= (1, 6): - from django.utils.module_loading import import_by_path - path_to_import = import_by_path +if django.VERSION >= (1, 7): + from django.utils.module_loading import import_string as path_to_import + from importlib import import_module else: - from ajax.utils import import_by_path - path_to_import = import_by_path + # 1.4 LTS compatibility + from ajax.utils import import_by_path as path_to_import + from django.utils.importlib import import_module From 3feb888faaf301d87d0dcd8931079432b9a1887f Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 18:15:46 -0700 Subject: [PATCH 43/62] modernize fixes for py3 compatibility --- ajax/__init__.py | 1 + ajax/conf.py | 1 + ajax/decorators.py | 7 ++++--- ajax/encoders.py | 6 ++++-- ajax/endpoints.py | 10 ++++++---- ajax/exceptions.py | 6 ++---- ajax/middleware/DebugToolbar.py | 12 +++++++----- ajax/signals.py | 1 + ajax/urls.py | 1 + ajax/utils.py | 1 + ajax/views.py | 10 ++++------ tests/example/endpoints.py | 1 + tests/example/models.py | 1 + tests/example/tests.py | 20 ++++++++++++++------ tests/manage.py | 1 + tests/settings.py | 1 + tests/urls.py | 1 + 17 files changed, 51 insertions(+), 30 deletions(-) diff --git a/ajax/__init__.py b/ajax/__init__.py index 18d3be3..45579a6 100755 --- a/ajax/__init__.py +++ b/ajax/__init__.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from ajax.endpoints import Endpoints from ajax.encoders import Encoders diff --git a/ajax/conf.py b/ajax/conf.py index 959ff65..9769cd3 100644 --- a/ajax/conf.py +++ b/ajax/conf.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.conf import settings from appconf import AppConf diff --git a/ajax/decorators.py b/ajax/decorators.py index 9546e76..3d49bf5 100644 --- a/ajax/decorators.py +++ b/ajax/decorators.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.utils.translation import ugettext as _ from django.utils.log import getLogger from django.http import Http404 @@ -61,7 +62,7 @@ def json_response(f, *args, **kwargs): result = f(*args, **kwargs) if isinstance(result, AJAXError): raise result - except AJAXError, e: + except AJAXError as e: result = e.get_response() request = args[0] @@ -72,9 +73,9 @@ def json_response(f, *args, **kwargs): 'request': request } ) - except Http404, e: + except Http404 as e: result = AJAXError(404, e.__str__()).get_response() - except Exception, e: + except Exception as e: import sys exc_info = sys.exc_info() type, message, trace = exc_info diff --git a/ajax/encoders.py b/ajax/encoders.py index 75bcd34..dc78699 100644 --- a/ajax/encoders.py +++ b/ajax/encoders.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.core import serializers from ajax.exceptions import AlreadyRegistered, NotRegistered from django.db.models.fields import FieldDoesNotExist @@ -7,6 +8,7 @@ from django.db.models.query import QuerySet from django.utils.encoding import smart_str import collections +import six # Used to change the field name for the Model's pk. @@ -45,7 +47,7 @@ def to_dict(self, record, expand=False, html_escape=False, fields=None): ret.update(data['fields']) ret[AJAX_PK_ATTR_NAME] = data['pk'] - for field, val in ret.iteritems(): + for field, val in six.iteritems(ret): try: f = record.__class__._meta.get_field(field) if expand and isinstance(f, models.ForeignKey): @@ -58,7 +60,7 @@ def to_dict(self, record, expand=False, html_escape=False, fields=None): new_value = self._encode_value(f, val) ret[smart_str(field)] = new_value - except FieldDoesNotExist, e: + except FieldDoesNotExist as e: pass # Assume extra fields are already safe. if expand and hasattr(record, 'tags') and \ diff --git a/ajax/endpoints.py b/ajax/endpoints.py index 87727ec..d5d2873 100644 --- a/ajax/endpoints.py +++ b/ajax/endpoints.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.core.exceptions import ValidationError from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.db import models @@ -11,6 +12,7 @@ from ajax.encoders import encoder from ajax.signals import ajax_created, ajax_deleted, ajax_updated from ajax.views import EnvelopedResponse +import six try: from taggit.utils import parse_tags @@ -126,7 +128,7 @@ def _save(self, record): record.full_clean() record.save() return record - except ValidationError, e: + except ValidationError as e: raise AJAXError(400, _("Could not save model."), errors=e.message_dict) @@ -136,7 +138,7 @@ def update(self, request): modified = self._get_record() update_record = False - for key, val in self._extract_data(request).iteritems(): + for key, val in six.iteritems(self._extract_data(request)): # Only setattr and save the model when a change has happened. if val != getattr(record, key): @@ -192,7 +194,7 @@ def _extract_tags(self, request): if raw_tags: try: tags = [t for t in parse_tags(raw_tags) if len(t)] - except Exception, e: + except Exception as e: pass return tags @@ -208,7 +210,7 @@ def _extract_data(self, request): load up that record. """ data = {} - for field, val in request.POST.iteritems(): + for field, val in six.iteritems(request.POST): if field in self.immutable_fields: continue # Ignore immutable fields silently. diff --git a/ajax/exceptions.py b/ajax/exceptions.py index 994b5d2..0f4c96c 100644 --- a/ajax/exceptions.py +++ b/ajax/exceptions.py @@ -1,7 +1,5 @@ -try: - import json -except ImportError: - from django.utils import simplejson as json +from __future__ import absolute_import +import json from django.utils.encoding import smart_str from django.http import HttpResponse, HttpResponseNotFound, \ diff --git a/ajax/middleware/DebugToolbar.py b/ajax/middleware/DebugToolbar.py index b29c033..048782b 100644 --- a/ajax/middleware/DebugToolbar.py +++ b/ajax/middleware/DebugToolbar.py @@ -1,8 +1,7 @@ -try: - import json -except ImportError: - from django.utils import simplejson as json +from __future__ import absolute_import +import json +from django.utils import six from debug_toolbar.middleware import DebugToolbarMiddleware, add_content_handler from django.core.serializers.json import DjangoJSONEncoder @@ -21,7 +20,10 @@ class AJAXDebugToolbarMiddleware(DebugToolbarMiddleware): the django-debug-toolbar panels. """ def _append_json(self, response, toolbar): - payload = json.loads(response.content) + if isinstance(response.content, six.text_type): + payload = json.loads(response.content) + else: + payload = json.loads(response.content.decode('utf-8')) payload['debug_toolbar'] = { 'sql': toolbar.stats['sql'], 'timer': toolbar.stats['timer'] diff --git a/ajax/signals.py b/ajax/signals.py index c1e1c68..aaf1609 100644 --- a/ajax/signals.py +++ b/ajax/signals.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import django.dispatch ajax_created = django.dispatch.Signal(providing_args=['instance']) diff --git a/ajax/urls.py b/ajax/urls.py index af4289c..0a8f20f 100644 --- a/ajax/urls.py +++ b/ajax/urls.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.conf.urls import * from django.views.static import serve import os diff --git a/ajax/utils.py b/ajax/utils.py index ecd8a38..c08e500 100644 --- a/ajax/utils.py +++ b/ajax/utils.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import sys from django.core.exceptions import ImproperlyConfigured diff --git a/ajax/views.py b/ajax/views.py index d4bdfdb..c2f32e0 100755 --- a/ajax/views.py +++ b/ajax/views.py @@ -1,16 +1,14 @@ -try: - import json -except ImportError: - from django.utils import simplejson as json +from __future__ import absolute_import +import json from django.conf import settings from django.http import HttpResponse from django.utils.translation import ugettext as _ -from django.utils.importlib import import_module from django.utils.log import getLogger from django.core.serializers.json import DjangoJSONEncoder from ajax.exceptions import AJAXError, NotRegistered from ajax.decorators import json_response +from ajax.compat import import_module import ajax @@ -44,7 +42,7 @@ def endpoint_loader(request, application, model, **kwargs): try: module = import_module('%s.endpoints' % application) - except ImportError, e: + except ImportError as e: if settings.DEBUG: raise e else: diff --git a/tests/example/endpoints.py b/tests/example/endpoints.py index f90153b..37be850 100644 --- a/tests/example/endpoints.py +++ b/tests/example/endpoints.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from ajax import endpoint from ajax.decorators import login_required from ajax.endpoints import ModelEndpoint diff --git a/tests/example/models.py b/tests/example/models.py index ff1414c..e25b2fd 100644 --- a/tests/example/models.py +++ b/tests/example/models.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.db import models class Category(models.Model): diff --git a/tests/example/tests.py b/tests/example/tests.py index bf4dcca..8888e66 100644 --- a/tests/example/tests.py +++ b/tests/example/tests.py @@ -1,9 +1,12 @@ +from __future__ import absolute_import +from __future__ import print_function import json import mock from django.test import TestCase from django.contrib.auth.models import User +from django.utils import six from ajax.endpoints import ModelEndpoint from ajax.exceptions import AJAXError @@ -39,12 +42,14 @@ def post(self, uri, data={}, debug=False, status_code=200): """ response = self.client.post(uri, data) if debug: - print response.__class__.__name__ - print response + print(response.__class__.__name__) + print(response) self.assertEquals(status_code, response.status_code) - - return response, json.loads(response.content) + if isinstance(response.content, six.text_type): + return response, json.loads(response.content) + else: + return response, json.loads(response.content.decode('utf-8')) class EncodeTests(BaseTest): @@ -168,8 +173,11 @@ def test_can_request_list_with_total(self): self.client.login(username='test', password='password') resp = self.client.post('/ajax/example/widget/list.json') - content = json.loads(resp.content) - self.assertTrue('total' in content.keys()) + if isinstance(resp.content, six.text_type): + content = json.loads(resp.content) + else: + content = json.loads(resp.content.decode('utf-8')) + self.assertTrue('total' in list(content.keys())) self.assertEquals(content['total'], 3) def test_delete(self): diff --git a/tests/manage.py b/tests/manage.py index 4d929b7..1b0b6ed 100755 --- a/tests/manage.py +++ b/tests/manage.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import #!/usr/bin/env python import os, sys diff --git a/tests/settings.py b/tests/settings.py index a4badb9..8d1a702 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import sys sys.path.insert(0, '../') diff --git a/tests/urls.py b/tests/urls.py index 5117a13..0e725b3 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.conf.urls import patterns, include, url From c58d4bf7ad33e6a874d8d1dcb66de70d1f6efc0e Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 18:20:29 -0700 Subject: [PATCH 44/62] Supported python versions updated --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22c3508..5627260 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,14 @@ language: python notifications: email: false env: -- DJANGO_VERSION=1.4 -- DJANGO_VERSION=1.5 -- DJANGO_VERSION=1.6 +- DJANGO_VERSION=1.4.* +- DJANGO_VERSION=1.7.* +- DJANGO_VERSION=1.8.* python: - '2.6' - '2.7' +- '3.3' +- '3.4' install: - pip install -r ci.txt Django==$DJANGO_VERSION - python setup.py install From 2b35888dd37451e7a99acf41ed356e04a4185fc3 Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 18:27:36 -0700 Subject: [PATCH 45/62] major version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c80a79a..de3fa5d 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='ajax', - version='2.0.0', + version='3.0.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From 2631da5f10d73cca3423507cd057da2d17831a4f Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 18:54:52 -0700 Subject: [PATCH 46/62] fix ci testing --- ci.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/ci.txt b/ci.txt index 200925f..462109c 100644 --- a/ci.txt +++ b/ci.txt @@ -1,4 +1,3 @@ decorator django-appconf~=1.0.0 django-jenkins -django>=1.4.*,<1.9,!=1.5.*,!=1.6.* \ No newline at end of file From 8580efab1e4617a2f0e236ae192dc3702a6c8638 Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 19:04:17 -0700 Subject: [PATCH 47/62] further fixing travis --- .travis.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5627260..395a6bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,3 +14,16 @@ install: - pip install -r ci.txt Django==$DJANGO_VERSION - python setup.py install script: make test +matrix: + exclude: + python: "3.3" + enc: DJANGO_VERSION=1.4.* + exclude: + python: "3.4" + enc: DJANGO_VERSION=1.4.* + exclude: + python: "2.6" + enc: DJANGO_VERSION=1.7.* + exclude: + python: "2.6" + enc: DJANGO_VERSION=1.8.* From 6386c8d9bf9c7db7d058d8382fe71db66f4d5240 Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 19:07:52 -0700 Subject: [PATCH 48/62] finally got the matrix exclude right? --- .travis.yml | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 395a6bc..b01245a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,17 +13,15 @@ python: install: - pip install -r ci.txt Django==$DJANGO_VERSION - python setup.py install -script: make test +script: +- make test matrix: exclude: - python: "3.3" - enc: DJANGO_VERSION=1.4.* - exclude: - python: "3.4" - enc: DJANGO_VERSION=1.4.* - exclude: - python: "2.6" - enc: DJANGO_VERSION=1.7.* - exclude: - python: "2.6" - enc: DJANGO_VERSION=1.8.* + - python: "3.3" + enc: DJANGO_VERSION=1.4.* + - python: "3.4" + enc: DJANGO_VERSION=1.4.* + - python: "2.6" + enc: DJANGO_VERSION=1.7.* + - python: "2.6" + enc: DJANGO_VERSION=1.8.* From c983bd0821d2f6f6321eb01769b090da0f232055 Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 19:19:08 -0700 Subject: [PATCH 49/62] debumped major version, that can be done after merging --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index de3fa5d..c80a79a 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='ajax', - version='3.0.0', + version='2.0.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From 91657e9d4bb7a8eaf41fafa8bd7a7b8dbdd2c0d2 Mon Sep 17 00:00:00 2001 From: Justin McClure Date: Thu, 6 Aug 2015 19:26:32 -0700 Subject: [PATCH 50/62] matrix... --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index b01245a..77b02e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,18 +10,18 @@ python: - '2.7' - '3.3' - '3.4' -install: -- pip install -r ci.txt Django==$DJANGO_VERSION -- python setup.py install -script: -- make test matrix: exclude: - python: "3.3" - enc: DJANGO_VERSION=1.4.* + env: DJANGO_VERSION=1.4.* - python: "3.4" - enc: DJANGO_VERSION=1.4.* + env: DJANGO_VERSION=1.4.* - python: "2.6" - enc: DJANGO_VERSION=1.7.* + env: DJANGO_VERSION=1.7.* - python: "2.6" - enc: DJANGO_VERSION=1.8.* + env: DJANGO_VERSION=1.8.* +install: +- pip install -r ci.txt Django==$DJANGO_VERSION +- python setup.py install +script: +- make test From 74885c5740c7a277732c3326cdfd09d56600c832 Mon Sep 17 00:00:00 2001 From: Justin Mcclure Date: Thu, 3 Dec 2015 13:55:55 -0800 Subject: [PATCH 51/62] Add getLogger to ajax.compat --- ajax/compat.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ajax/compat.py b/ajax/compat.py index 992da11..7bcf8d6 100644 --- a/ajax/compat.py +++ b/ajax/compat.py @@ -4,7 +4,9 @@ if django.VERSION >= (1, 7): from django.utils.module_loading import import_string as path_to_import from importlib import import_module + from logging import getLogger else: # 1.4 LTS compatibility from ajax.utils import import_by_path as path_to_import from django.utils.importlib import import_module + from django.utils.log import getLogger From 06df3caa979efe032aba3f1668c152376121b050 Mon Sep 17 00:00:00 2001 From: Justin Mcclure Date: Thu, 3 Dec 2015 13:56:20 -0800 Subject: [PATCH 52/62] Use ajax.compat imports --- ajax/decorators.py | 2 +- ajax/utils.py | 2 +- ajax/views.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ajax/decorators.py b/ajax/decorators.py index 3d49bf5..7f0c997 100644 --- a/ajax/decorators.py +++ b/ajax/decorators.py @@ -1,6 +1,6 @@ from __future__ import absolute_import from django.utils.translation import ugettext as _ -from django.utils.log import getLogger +from ajax.compat import getLogger from django.http import Http404 from django.conf import settings from decorator import decorator diff --git a/ajax/utils.py b/ajax/utils.py index c08e500..d0f59ab 100644 --- a/ajax/utils.py +++ b/ajax/utils.py @@ -2,7 +2,7 @@ import sys from django.core.exceptions import ImproperlyConfigured -from django.utils.importlib import import_module +from ajax.compat import import_module def import_by_path(dotted_path, error_prefix=''): diff --git a/ajax/views.py b/ajax/views.py index c2f32e0..bbc647a 100755 --- a/ajax/views.py +++ b/ajax/views.py @@ -4,7 +4,7 @@ from django.conf import settings from django.http import HttpResponse from django.utils.translation import ugettext as _ -from django.utils.log import getLogger +from ajax.compat import getLogger from django.core.serializers.json import DjangoJSONEncoder from ajax.exceptions import AJAXError, NotRegistered from ajax.decorators import json_response From 2a0040ec197f7a0f7e4fc4e6410798b61f5a10a3 Mon Sep 17 00:00:00 2001 From: Justin Mcclure Date: Thu, 3 Dec 2015 14:48:54 -0800 Subject: [PATCH 53/62] Bump tests to currently supported versions --- .travis.yml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 77b02e7..1ada6d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,24 +2,14 @@ language: python notifications: email: false env: -- DJANGO_VERSION=1.4.* -- DJANGO_VERSION=1.7.* -- DJANGO_VERSION=1.8.* +- DJANGO_VERSION~=1.7.0 +- DJANGO_VERSION~=1.8.0 +- DJANGO_VERSION~=1.9.0 python: -- '2.6' - '2.7' - '3.3' - '3.4' -matrix: - exclude: - - python: "3.3" - env: DJANGO_VERSION=1.4.* - - python: "3.4" - env: DJANGO_VERSION=1.4.* - - python: "2.6" - env: DJANGO_VERSION=1.7.* - - python: "2.6" - env: DJANGO_VERSION=1.8.* +- '3.5' install: - pip install -r ci.txt Django==$DJANGO_VERSION - python setup.py install From f79ed0194d562bcb62a57dba86a73665fd159a47 Mon Sep 17 00:00:00 2001 From: Justin Mcclure Date: Thu, 3 Dec 2015 14:52:19 -0800 Subject: [PATCH 54/62] fix testing --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ada6d5..e41c1a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,16 +2,16 @@ language: python notifications: email: false env: -- DJANGO_VERSION~=1.7.0 -- DJANGO_VERSION~=1.8.0 -- DJANGO_VERSION~=1.9.0 +- DJANGO_VERSION='django~=1.7.0' +- DJANGO_VERSION='django~=1.8.0' +- DJANGO_VERSION='django~=1.9.0' python: - '2.7' - '3.3' - '3.4' - '3.5' install: -- pip install -r ci.txt Django==$DJANGO_VERSION +- pip install -r ci.txt $DJANGO_VERSION - python setup.py install script: - make test From f43f810ca56a239e6c26d5b2ea631d3919ba5c5b Mon Sep 17 00:00:00 2001 From: Justin Mcclure Date: Thu, 3 Dec 2015 15:00:23 -0800 Subject: [PATCH 55/62] fix matrix excludes --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index e41c1a9..7e383c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ env: - DJANGO_VERSION='django~=1.9.0' python: - '2.7' +- '3.2' - '3.3' - '3.4' - '3.5' @@ -15,3 +16,11 @@ install: - python setup.py install script: - make test +matrix: + exclude: + - python: '3.5' + env: DJANGO_VERSION='django~=1.7.0' + - python: '3.2' + env: DJANGO_VERSION='django~=1.9.0' + - python: '3.3' + env: DJANGO_VERSION='django~=1.9.0' From 5b4e7c07945c5dde2f3a53588ad0233df39b4486 Mon Sep 17 00:00:00 2001 From: Justin Mcclure Date: Thu, 3 Dec 2015 16:37:44 -0800 Subject: [PATCH 56/62] Simplify yml --- .travis.yml | 14 +++++++------- tests/manage.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7e383c3..e4450c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ language: python notifications: email: false env: -- DJANGO_VERSION='django~=1.7.0' -- DJANGO_VERSION='django~=1.8.0' -- DJANGO_VERSION='django~=1.9.0' +- DJANGO_VERSION=1.7.0 +- DJANGO_VERSION=1.8.0 +- DJANGO_VERSION=1.9.0 python: - '2.7' - '3.2' @@ -12,15 +12,15 @@ python: - '3.4' - '3.5' install: -- pip install -r ci.txt $DJANGO_VERSION +- pip install -r ci.txt django~=$DJANGO_VERSION - python setup.py install script: - make test matrix: exclude: - python: '3.5' - env: DJANGO_VERSION='django~=1.7.0' + env: DJANGO_VERSION=1.7.0 - python: '3.2' - env: DJANGO_VERSION='django~=1.9.0' + env: DJANGO_VERSION=1.9.0 - python: '3.3' - env: DJANGO_VERSION='django~=1.9.0' + env: DJANGO_VERSION=1.9.0 diff --git a/tests/manage.py b/tests/manage.py index 1b0b6ed..1379e50 100755 --- a/tests/manage.py +++ b/tests/manage.py @@ -1,5 +1,5 @@ -from __future__ import absolute_import #!/usr/bin/env python +from __future__ import absolute_import import os, sys if __name__ == "__main__": From 9c7d5bb70dfe66e2268d79cc395ac3632e565045 Mon Sep 17 00:00:00 2001 From: Aaron DeVore Date: Mon, 14 Dec 2015 10:57:52 -0800 Subject: [PATCH 57/62] Change django-appconf version spec to work with RHEL6's pip --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c80a79a..8883cd4 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ zip_safe=False, install_requires=[ 'decorator', - 'django-appconf==1.0.*', + 'django-appconf>=1.0.0,<1.1', ], extras_require={ 'Tagging': ['taggit'] From 2165526a52cac78cde1afc3ff0b501f35d09bb87 Mon Sep 17 00:00:00 2001 From: Joe Stump Date: Wed, 27 Apr 2016 15:46:36 -0700 Subject: [PATCH 58/62] import_module and getLogger are both deprecated in Django 1.7+ now. --- ajax/compat.py | 9 +++++---- ajax/decorators.py | 4 ++-- ajax/views.py | 8 ++++---- setup.py | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/ajax/compat.py b/ajax/compat.py index af729ca..519600b 100644 --- a/ajax/compat.py +++ b/ajax/compat.py @@ -1,8 +1,9 @@ import django -if django.VERSION >= (1, 6): - from django.utils.module_loading import import_by_path +if django.VERSION >= (1, 7): + from django.utils.module_loading import import_string as path_to_import +elif django.VERSION >= (1, 6): + from django.utils.module_loading import import_by_path as path_to_import path_to_import = import_by_path else: - from ajax.utils import import_by_path - path_to_import = import_by_path + from ajax.utils import import_by_path as path_to_import diff --git a/ajax/decorators.py b/ajax/decorators.py index 9546e76..adf7601 100644 --- a/ajax/decorators.py +++ b/ajax/decorators.py @@ -1,14 +1,14 @@ from django.utils.translation import ugettext as _ -from django.utils.log import getLogger from django.http import Http404 from django.conf import settings from decorator import decorator from ajax.exceptions import AJAXError, PrimaryKeyMissing from functools import wraps from django.utils.decorators import available_attrs +import logging -logger = getLogger('django.request') +logger = logging.getLogger('django.request') @decorator diff --git a/ajax/views.py b/ajax/views.py index d4bdfdb..e838c87 100755 --- a/ajax/views.py +++ b/ajax/views.py @@ -6,15 +6,15 @@ from django.conf import settings from django.http import HttpResponse from django.utils.translation import ugettext as _ -from django.utils.importlib import import_module -from django.utils.log import getLogger from django.core.serializers.json import DjangoJSONEncoder from ajax.exceptions import AJAXError, NotRegistered from ajax.decorators import json_response +from ajax.compat import path_to_import import ajax +import logging -logger = getLogger('django.request') +logger = logging.getLogger('django.request') class EnvelopedResponse(object): @@ -43,7 +43,7 @@ def endpoint_loader(request, application, model, **kwargs): raise AJAXError(400, _('Invalid HTTP method used.')) try: - module = import_module('%s.endpoints' % application) + module = path_to_import('%s.endpoints' % application) except ImportError, e: if settings.DEBUG: raise e diff --git a/setup.py b/setup.py index a9a81fe..bb82a9c 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ zip_safe=False, install_requires=[ 'decorator', - 'django-appconf==0.6', + 'django-appconf==1.0.2', ], extras_require={ 'Tagging': ['taggit'] From aae9ec849fbd677cb0ec6c0b6359e278567b0ce1 Mon Sep 17 00:00:00 2001 From: Joe Stump Date: Wed, 27 Apr 2016 15:55:56 -0700 Subject: [PATCH 59/62] Revert "import_module and getLogger are both deprecated in Django 1.7+ now." This reverts commit 2165526a52cac78cde1afc3ff0b501f35d09bb87. --- ajax/compat.py | 9 ++++----- ajax/decorators.py | 4 ++-- ajax/views.py | 8 ++++---- setup.py | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ajax/compat.py b/ajax/compat.py index 519600b..af729ca 100644 --- a/ajax/compat.py +++ b/ajax/compat.py @@ -1,9 +1,8 @@ import django -if django.VERSION >= (1, 7): - from django.utils.module_loading import import_string as path_to_import -elif django.VERSION >= (1, 6): - from django.utils.module_loading import import_by_path as path_to_import +if django.VERSION >= (1, 6): + from django.utils.module_loading import import_by_path path_to_import = import_by_path else: - from ajax.utils import import_by_path as path_to_import + from ajax.utils import import_by_path + path_to_import = import_by_path diff --git a/ajax/decorators.py b/ajax/decorators.py index adf7601..9546e76 100644 --- a/ajax/decorators.py +++ b/ajax/decorators.py @@ -1,14 +1,14 @@ from django.utils.translation import ugettext as _ +from django.utils.log import getLogger from django.http import Http404 from django.conf import settings from decorator import decorator from ajax.exceptions import AJAXError, PrimaryKeyMissing from functools import wraps from django.utils.decorators import available_attrs -import logging -logger = logging.getLogger('django.request') +logger = getLogger('django.request') @decorator diff --git a/ajax/views.py b/ajax/views.py index e838c87..d4bdfdb 100755 --- a/ajax/views.py +++ b/ajax/views.py @@ -6,15 +6,15 @@ from django.conf import settings from django.http import HttpResponse from django.utils.translation import ugettext as _ +from django.utils.importlib import import_module +from django.utils.log import getLogger from django.core.serializers.json import DjangoJSONEncoder from ajax.exceptions import AJAXError, NotRegistered from ajax.decorators import json_response -from ajax.compat import path_to_import import ajax -import logging -logger = logging.getLogger('django.request') +logger = getLogger('django.request') class EnvelopedResponse(object): @@ -43,7 +43,7 @@ def endpoint_loader(request, application, model, **kwargs): raise AJAXError(400, _('Invalid HTTP method used.')) try: - module = path_to_import('%s.endpoints' % application) + module = import_module('%s.endpoints' % application) except ImportError, e: if settings.DEBUG: raise e diff --git a/setup.py b/setup.py index bb82a9c..a9a81fe 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ zip_safe=False, install_requires=[ 'decorator', - 'django-appconf==1.0.2', + 'django-appconf==0.6', ], extras_require={ 'Tagging': ['taggit'] From a28632c70714075f98f60cae060575fb34b2e1ef Mon Sep 17 00:00:00 2001 From: Joe Stump Date: Wed, 27 Apr 2016 15:56:51 -0700 Subject: [PATCH 60/62] Remove a couple of versions that don't appear to work. --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e4450c6..44f1265 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ env: - DJANGO_VERSION=1.9.0 python: - '2.7' -- '3.2' - '3.3' - '3.4' - '3.5' @@ -20,7 +19,5 @@ matrix: exclude: - python: '3.5' env: DJANGO_VERSION=1.7.0 - - python: '3.2' - env: DJANGO_VERSION=1.9.0 - python: '3.3' env: DJANGO_VERSION=1.9.0 From ba6ebb4231dd1d6e308fee3a8ee3e0ee41aecb44 Mon Sep 17 00:00:00 2001 From: Joe Stump Date: Wed, 27 Apr 2016 16:01:06 -0700 Subject: [PATCH 61/62] Bump the major version as Django versions are being dropped. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8883cd4..3c88e40 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='ajax', - version='2.0.0', + version='3.0.0', description='A simple framework for creating AJAX endpoints in Django.', long_description='', keywords='django, ajax', From 069099eaa3fa34b514e6785e154aabfdd184547b Mon Sep 17 00:00:00 2001 From: Aaron DeVore Date: Fri, 3 Jun 2016 11:15:56 -0700 Subject: [PATCH 62/62] Django 1.9 support: Remove dependency on django.conf.urls.patterns --- ajax/urls.py | 25 ++++++++++++++++++------- tests/urls.py | 18 +++++++++++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/ajax/urls.py b/ajax/urls.py index 0a8f20f..790b182 100644 --- a/ajax/urls.py +++ b/ajax/urls.py @@ -1,14 +1,25 @@ from __future__ import absolute_import from django.conf.urls import * from django.views.static import serve +from ajax import views +import django import os JAVASCRIPT_PATH = "%s/js" % os.path.dirname(__file__) -urlpatterns = patterns('ajax.views', - (r'^(?P\w+)/(?P\w+).json', 'endpoint_loader'), - (r'^(?P\w+)/(?P\w+)/(?P\w+).json', 'endpoint_loader'), - (r'^(?P\w+)/(?P\w+)/(?P\d+)/(?P\w+)/?(?P(add|remove|set|clear|similar))?.json$', 'endpoint_loader'), - (r'^js/(?P.*)$', serve, - {'document_root': JAVASCRIPT_PATH}), -) +if django.VERSION < (1, 8): + urlpatterns = patterns('ajax.views', + (r'^(?P\w+)/(?P\w+).json', 'endpoint_loader'), + (r'^(?P\w+)/(?P\w+)/(?P\w+).json', 'endpoint_loader'), + (r'^(?P\w+)/(?P\w+)/(?P\d+)/(?P\w+)/?(?P(add|remove|set|clear|similar))?.json$', 'endpoint_loader'), + (r'^js/(?P.*)$', serve, + {'document_root': JAVASCRIPT_PATH}), + ) +else: + urlpatterns = [ + url(http://github.com/r'%5E(?P%3Capplication%3E\w+)/(?P\w+).json', views.endpoint_loader), + url(http://github.com/r'%5E(?P%3Capplication%3E\w+)/(?P\w+)/(?P\w+).json', views.endpoint_loader), + url(http://github.com/r'%5E(?P%3Capplication%3E\w+)/(?P\w+)/(?P\d+)/(?P\w+)/?(?P(add|remove|set|clear|similar))?.json$', views.endpoint_loader), + url(http://github.com/r'%5Ejs/(?P%3Cpath%3E.*)$', serve, + {'document_root': JAVASCRIPT_PATH}), + ] diff --git a/tests/urls.py b/tests/urls.py index 0e725b3..312f929 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,7 +1,15 @@ from __future__ import absolute_import -from django.conf.urls import patterns, include, url +import django +try: + from django.conf.urls import patterns, include, url +except ImportError: + from django.conf.urls import include, url - -urlpatterns = patterns('', - url(http://github.com/r'%5Eajax/',%20include('ajax.urls')), -) +if django.VERSION < (1, 8): + urlpatterns = patterns('', + url(http://github.com/r'%5Eajax/',%20include('ajax.urls')), + ) +else: + urlpatterns = [ + url(http://github.com/r'%5Eajax/',%20include('ajax.urls')) + ]