├── LICENSE ├── README.md ├── angular-django-rest-resource.js ├── bower.json └── test ├── django_project ├── __init__.py ├── manage.py ├── settings.py ├── test_rest │ ├── __init__.py │ ├── admin.py │ ├── fixtures │ │ └── initial_data.json │ ├── models.py │ ├── serializers.py │ ├── static │ │ └── index.html │ ├── tests.py │ └── views.py ├── urls.py └── wsgi.py └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | Except where otherwise noted: 2 | 3 | The MIT License 4 | 5 | Copyright (c) 2013 BlackLocus (http://blacklocus.com) and contributors 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | 26 | 27 | 28 | Portions from angular.js: 29 | 30 | The MIT License 31 | 32 | Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy 35 | of this software and associated documentation files (the "Software"), to deal 36 | in the Software without restriction, including without limitation the rights 37 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 38 | copies of the Software, and to permit persons to whom the Software is 39 | furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in 42 | all copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 47 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 49 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 50 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-django-rest-resource 2 | ============================ 3 | 4 | An AngularJS module that provides a resource-generation service similar to ngResource, but optimized for the 5 | Django REST Framework. The biggest features: 6 | 7 | * Trailing slashes allowed in the resource URLs per the Django community norm. 8 | * The `isArray` methods like `query` allow for paginated responses. The pages will be streamed into the promise object. 9 | 10 | Installation 11 | ------------ 12 | Download angular-django-rest-resource.js and put it in your project. You may also do it the bower way: 13 | 14 | bower install angular-django-rest-resource 15 | 16 | Usage 17 | ----- 18 | Do this somewhere in your application HTML: 19 | 20 | 21 | 22 | Add this AngularJS module as a dependency to your AngularJS application: 23 | 24 | angular.module('app', [..., 'djangoRESTResources']); 25 | 26 | (where 'app' is whatever you've named your AngularJS application). 27 | 28 | 29 | In your controllers and anything that needs to interact with the Django REST Framework services, inject the `djResource` 30 | service. Then you can create class-like objects that represent (and interact with) your Django REST Framework resources: 31 | 32 | var Poll = djResource('/polls/:pollId/', {pollId:'@id'}); 33 | 34 | var myPoll = Poll.get({pollId:86}, function() { 35 | myPoll.readByUser = true; 36 | myPoll.$save(); 37 | }); 38 | 39 | For complete API, consider the documentation for [$resource](http://docs.angularjs.org/api/ngResource.$resource), as 40 | this module follows the API quite closely. 41 | 42 | Launching The Testing and Demonstration App 43 | ------------------------------------------- 44 | This is a work in progress, but it gives some hints on usage in a real world scenario. 45 | 46 | 1. Create a Python virtualenv. 47 | 2. Install the dependencies: 48 | 49 | cd test 50 | pip install -r requirements.txt 51 | 52 | 3. Set up the database and fixtures (if first time use). 53 | 54 | python manage.py syncdb 55 | 56 | 4. Run the server: 57 | 58 | python manage.py runserver 59 | 60 | 5. Point web browser to `http://localhost:8000/`. -------------------------------------------------------------------------------- /angular-django-rest-resource.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Portions of this file: 4 | //Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 5 | //Those portions modified, used, or copied under permissions granted by the MIT license. See: 6 | // https://raw.github.com/angular/angular.js/9480136d9f062ec4b8df0a35914b48c0d61e0002/LICENSE 7 | 8 | /** 9 | * @ngdoc overview 10 | * @name djangoRESTResources 11 | * @description 12 | */ 13 | 14 | /** 15 | * @ngdoc object 16 | * @name djangoRESTResources.djResource 17 | * @requires $http 18 | * 19 | * @description 20 | * A factory for generating classes that interact with a Django REST Framework backend. 21 | * 22 | * Identical in operation to AngularJS' ngResource module's $resource object except for the following: 23 | * - If an isArray=True request receives a JSON _object_ containing a `count` field (instead of a JS array), assume 24 | * that the REST endpoint has `paginate_by` set. The results are then streamed a page at a time into the promise object 25 | * and any success callbacks are deferred until the last page returns successfully. 26 | * - URLs are assumed to have the trailing slashes, as is the Django way of doing things. 27 | * 28 | * # Installation 29 | * Include `angular-django-rest-resource.js` 30 | * 31 | * Load the module: 32 | * 33 | * angular.module('app', ['djangoRESTResources']); 34 | * 35 | * now you inject djResource into any of your Angular things. 36 | * 37 | * @param {string} url A parametrized URL template with parameters prefixed by `:` as in 38 | * `/user/:username`. If you are using a URL with a port number (e.g. 39 | * `http://example.com:8080/api`), it will be respected. 40 | * 41 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 42 | * `actions` methods. If any of the parameter value is a function, it will be executed every time 43 | * when a param value needs to be obtained for a request (unless the param was overridden). 44 | * 45 | * Each key value in the parameter object is first bound to url template if present and then any 46 | * excess keys are appended to the url search query after the `?`. 47 | * 48 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 49 | * URL `/path/greet?salutation=Hello`. 50 | * 51 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 52 | * the data object (useful for non-GET operations). 53 | * 54 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 55 | * default set of resource actions. The declaration should be created in the format of $http.config 56 | * 57 | * {action1: {method:?, params:?, isArray:?, headers:?, ...}, 58 | * action2: {method:?, params:?, isArray:?, headers:?, ...}, 59 | * ...} 60 | * 61 | * Where: 62 | * 63 | * - **`action`** – {string} – The name of action. This name becomes the name of the method on your 64 | * resource object. 65 | * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 66 | * and `JSONP`. 67 | * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of the 68 | * parameter value is a function, it will be executed every time when a param value needs to be 69 | * obtained for a request (unless the param was overridden). 70 | * - **`url`** – {string} – action specific `url` override. The url templating is supported just like 71 | * for the resource-level urls. 72 | * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, see 73 | * `returns` section. 74 | * - **`transformRequest`** – `{function(data, headersGetter)|Array.}` – 75 | * transform function or an array of such functions. The transform function takes the http 76 | * request body and headers and returns its transformed (typically serialized) version. 77 | * - **`transformResponse`** – `{function(data, headersGetter)|Array.}` – 78 | * transform function or an array of such functions. The transform function takes the http 79 | * response body and headers and returns its transformed (typically deserialized) version. 80 | * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the 81 | * GET request, otherwise if a cache instance built with 82 | * {@link http://docs.angularjs.org/api/ng.$cacheFactory $cacheFactory}, this cache will be used for 83 | * caching. 84 | * - **`timeout`** – `{number}` – timeout in milliseconds. 85 | * - **`withCredentials`** - `{boolean}` - whether to to set the `withCredentials` flag on the 86 | * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 87 | * requests with credentials} for more information. 88 | * - **`responseType`** - `{string}` - see 89 | * {@link https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. 90 | * 91 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 92 | * optionally extended with custom `actions`. The default set contains these actions: 93 | * 94 | * { 'get': {method:'GET'}, 95 | * 'save': {method:'POST', method_if_field_has_value:['id', 'PUT']}, 96 | * 'update': {method:'PUT'}, 97 | * 'query': {method:'GET', isArray:true}, 98 | * 'remove': {method:'DELETE'}, 99 | * 'delete': {method:'DELETE'} }; 100 | * 101 | * Calling these methods invoke an {@link http://docs.angularjs.org/api/ng.$http $http} with the specified http 102 | * method, destination and parameters. When the data is returned from the server then the object is an 103 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 104 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 105 | * read, update, delete) on server-side data like this: 106 | *
107 |         var User = djResource('/user/:userId', {userId:'@id'});
108 |         var user = User.get({userId:123}, function() {
109 |           user.abc = true;
110 |           user.$save();
111 |         });
112 |      
113 | * 114 | * Invoking a djResource object method immediately returns an empty reference (object or array depending 115 | * on `isArray`). Once the data is returned from the server the existing reference is populated with the actual data. 116 | * 117 | * The action methods on the class object or instance object can be invoked with the following 118 | * parameters: 119 | * 120 | * - HTTP GET "class" actions: `DjangoRESTResource.action([parameters], [success], [error])` 121 | * - non-GET "class" actions: `DjangoRESTResource.action([parameters], postData, [success], [error])` 122 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 123 | * 124 | * 125 | * The DjangoRESTResource instances and collection have these additional properties: 126 | * 127 | * - `$then`: the `then` method of a {@link http://docs.angularjs.org/api/ng.$q promise} derived from the underlying 128 | * {@link http://docs.angularjs.org/api/ng.$http $http} call. 129 | * 130 | * The success callback for the `$then` method will be resolved if the underlying `$http` requests 131 | * succeeds. 132 | * 133 | * The success callback is called with a single object which is the 134 | * {@link http://docs.angularjs.org/api/ng.$http http response} 135 | * object extended with a new property `resource`. This `resource` property is a reference to the 136 | * result of the resource action — resource object or array of resources. 137 | * 138 | * The error callback is called with the {@link http://docs.angularjs.org/api/ng.$http http response} object when 139 | * an http error occurs. 140 | * 141 | * - `$resolved`: true if the promise has been resolved (either with success or rejection); 142 | * Knowing if the DjangoRESTResource has been resolved is useful in data-binding. 143 | */ 144 | angular.module('djangoRESTResources', ['ng']). 145 | factory('djResource', ['$http', '$parse', function($http, $parse) { 146 | var DEFAULT_ACTIONS = { 147 | 'get': {method:'GET'}, 148 | 'save': {method:'POST', method_if_field_has_value: ['id','PUT']}, 149 | 'update': {method:'PUT'}, 150 | 'query': {method:'GET', isArray:true}, 151 | 'remove': {method:'DELETE'}, 152 | 'delete': {method:'DELETE'} 153 | }; 154 | var noop = angular.noop, 155 | forEach = angular.forEach, 156 | extend = angular.extend, 157 | copy = angular.copy, 158 | isFunction = angular.isFunction, 159 | getter = function(obj, path) { 160 | return $parse(path)(obj); 161 | }; 162 | 163 | /** 164 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 165 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 166 | * segments: 167 | * segment = *pchar 168 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 169 | * pct-encoded = "%" HEXDIG HEXDIG 170 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 171 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 172 | * / "*" / "+" / "," / ";" / "=" 173 | */ 174 | function encodeUriSegment(val) { 175 | return encodeUriQuery(val, true). 176 | replace(/%26/gi, '&'). 177 | replace(/%3D/gi, '='). 178 | replace(/%2B/gi, '+'); 179 | } 180 | 181 | 182 | /** 183 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 184 | * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 185 | * encoded per http://tools.ietf.org/html/rfc3986: 186 | * query = *( pchar / "/" / "?" ) 187 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 188 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 189 | * pct-encoded = "%" HEXDIG HEXDIG 190 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 191 | * / "*" / "+" / "," / ";" / "=" 192 | */ 193 | function encodeUriQuery(val, pctEncodeSpaces) { 194 | return encodeURIComponent(val). 195 | replace(/%40/gi, '@'). 196 | replace(/%3A/gi, ':'). 197 | replace(/%24/g, '$'). 198 | replace(/%2C/gi, ','). 199 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 200 | } 201 | 202 | function Route(template, defaults) { 203 | this.template = template = template + '#'; 204 | this.defaults = defaults || {}; 205 | this.urlParams = {}; 206 | } 207 | 208 | Route.prototype = { 209 | setUrlParams: function(config, params, actionUrl) { 210 | var self = this, 211 | url = actionUrl || self.template, 212 | val, 213 | encodedVal; 214 | 215 | var urlParams = self.urlParams = {}; 216 | forEach(url.split(/\W/), function(param){ 217 | if (!(new RegExp("^\\d+$").test(param)) && param && (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { 218 | urlParams[param] = true; 219 | } 220 | }); 221 | url = url.replace(/\\:/g, ':'); 222 | 223 | params = params || {}; 224 | forEach(self.urlParams, function(_, urlParam){ 225 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 226 | if (angular.isDefined(val) && val !== null) { 227 | encodedVal = encodeUriSegment(val); 228 | url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1"); 229 | } else { 230 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, 231 | leadingSlashes, tail) { 232 | if (tail.charAt(0) == '/') { 233 | return tail; 234 | } else { 235 | return leadingSlashes + tail; 236 | } 237 | }); 238 | } 239 | }); 240 | 241 | // set the url 242 | config.url = url.replace(/#$/, ''); 243 | 244 | // set params - delegate param encoding to $http 245 | forEach(params, function(value, key){ 246 | if (!self.urlParams[key]) { 247 | config.params = config.params || {}; 248 | config.params[key] = value; 249 | } 250 | }); 251 | } 252 | }; 253 | 254 | 255 | function DjangoRESTResourceFactory(url, paramDefaults, actions) { 256 | var route = new Route(url); 257 | 258 | actions = extend({}, DEFAULT_ACTIONS, actions); 259 | 260 | function extractParams(data, actionParams){ 261 | var ids = {}; 262 | actionParams = extend({}, paramDefaults, actionParams); 263 | forEach(actionParams, function(value, key){ 264 | if (isFunction(value)) { value = value(); } 265 | ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 266 | }); 267 | return ids; 268 | } 269 | 270 | function DjangoRESTResource(value){ 271 | copy(value || {}, this); 272 | } 273 | 274 | forEach(actions, function(action, name) { 275 | action.method = angular.uppercase(action.method); 276 | var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; 277 | DjangoRESTResource[name] = function(a1, a2, a3, a4) { 278 | var params = {}; 279 | var data; 280 | var success = noop; 281 | var error = null; 282 | var promise; 283 | 284 | switch(arguments.length) { 285 | case 4: 286 | error = a4; 287 | success = a3; 288 | //fallthrough 289 | case 3: 290 | case 2: 291 | if (isFunction(a2)) { 292 | if (isFunction(a1)) { 293 | success = a1; 294 | error = a2; 295 | break; 296 | } 297 | 298 | success = a2; 299 | error = a3; 300 | //fallthrough 301 | } else { 302 | params = a1; 303 | data = a2; 304 | success = a3; 305 | break; 306 | } 307 | case 1: 308 | if (isFunction(a1)) success = a1; 309 | else if (hasBody) data = a1; 310 | else params = a1; 311 | break; 312 | case 0: break; 313 | default: 314 | throw "Expected between 0-4 arguments [params, data, success, error], got " + 315 | arguments.length + " arguments."; 316 | } 317 | 318 | var value = this instanceof DjangoRESTResource ? this : (action.isArray ? [] : new DjangoRESTResource(data)); 319 | var httpConfig = {}, 320 | promise; 321 | 322 | forEach(action, function(value, key) { 323 | if (key == 'method' && action.hasOwnProperty('method_if_field_has_value')) { 324 | // Check if the action's HTTP method is dependent on a field holding a value ('id' for example) 325 | var field = action.method_if_field_has_value[0]; 326 | var fieldDependentMethod = action.method_if_field_has_value[1]; 327 | httpConfig.method = 328 | (data.hasOwnProperty(field) && data[field] !== null) ? fieldDependentMethod : action.method; 329 | } else if (key != 'params' && key != 'isArray' ) { 330 | httpConfig[key] = copy(value); 331 | } 332 | }); 333 | httpConfig.data = data; 334 | route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params), action.url); 335 | 336 | function markResolved() { value.$resolved = true; } 337 | 338 | promise = $http(httpConfig); 339 | value.$resolved = false; 340 | 341 | promise.then(markResolved, markResolved); 342 | value.$then = promise.then(function(response) { 343 | // Success wrapper 344 | 345 | var data = response.data; 346 | var then = value.$then, resolved = value.$resolved; 347 | 348 | var deferSuccess = false; 349 | 350 | if (data) { 351 | if (action.isArray) { 352 | value.length = 0; 353 | 354 | // If it's an object with count and results, it's a pagination container, not an array: 355 | if (data.hasOwnProperty("count") && data.hasOwnProperty("results")) { 356 | // Don't call success callback until the last page has been accepted: 357 | deferSuccess = true; 358 | 359 | var paginator = function recursivePaginator(data) { 360 | // If there is a next page, go ahead and request it before parsing our results. Less wasted time. 361 | if (data.next !== null) { 362 | var next_config = copy(httpConfig); 363 | next_config.params = {}; 364 | next_config.url = data.next; 365 | $http(next_config).success(function(next_data) { recursivePaginator(next_data); }).error(error); 366 | } 367 | // Ok, now load this page's results: 368 | forEach(data.results, function(item) { 369 | value.push(new DjangoRESTResource(item)); 370 | }); 371 | if (data.next == null) { 372 | // We've reached the last page, call the original success callback with the concatenated pages of data. 373 | (success||noop)(value, response.headers); 374 | } 375 | }; 376 | paginator(data); 377 | } else { 378 | //Not paginated, push into array as normal. 379 | forEach(data, function(item) { 380 | value.push(new DjangoRESTResource(item)); 381 | }); 382 | } 383 | } else { 384 | // Not an isArray action 385 | copy(data, value); 386 | 387 | // Copy operation destroys value's original properties, so restore some of the old ones: 388 | value.$then = then; 389 | value.$resolved = resolved; 390 | value.$promise = promise; 391 | } 392 | } 393 | 394 | if (!deferSuccess) { 395 | (success||noop)(value, response.headers); 396 | } 397 | 398 | response.resource = value; 399 | return response; 400 | }, error).then; 401 | 402 | return value; 403 | }; 404 | 405 | 406 | DjangoRESTResource.prototype['$' + name] = function(a1, a2, a3) { 407 | var params = extractParams(this), 408 | success = noop, 409 | error; 410 | 411 | switch(arguments.length) { 412 | case 3: params = a1; success = a2; error = a3; break; 413 | case 2: 414 | case 1: 415 | if (isFunction(a1)) { 416 | success = a1; 417 | error = a2; 418 | } else { 419 | params = a1; 420 | success = a2 || noop; 421 | } 422 | case 0: break; 423 | default: 424 | throw "Expected between 1-3 arguments [params, success, error], got " + 425 | arguments.length + " arguments."; 426 | } 427 | var data = hasBody ? this : undefined; 428 | return DjangoRESTResource[name].call(this, params, data, success, error); 429 | }; 430 | }); 431 | 432 | DjangoRESTResource.bind = function(additionalParamDefaults){ 433 | return DjangoRESTResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 434 | }; 435 | 436 | return DjangoRESTResource; 437 | } 438 | 439 | return DjangoRESTResourceFactory; 440 | }]); 441 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-django-rest-resource", 3 | "main": "angular-django-rest-resource.js", 4 | "version": "1.0.3", 5 | "homepage": "https://github.com/blacklocus/angular-django-rest-resource", 6 | "authors": [ 7 | "BlackLocus", 8 | "Justin Turner Arthur", 9 | "Contributors" 10 | ], 11 | "description": "An AngularJS module that provides a resource-generation service similar to ngResource, but optimized for the Django REST Framework.", 12 | "keywords": [ 13 | "AngularJS", 14 | "Django", 15 | "REST", 16 | "Framework", 17 | "Django", 18 | "Angular", 19 | "paginate", 20 | "pagination" 21 | ], 22 | "license": "MIT", 23 | "ignore": [ 24 | "**/.*", 25 | "**/.md", 26 | "node_modules", 27 | "bower_components", 28 | "test", 29 | "tests" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /test/django_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blacklocus/angular-django-rest-resource/22c0631e8af4a31cc013471a9e0f219d1c6f6b66/test/django_project/__init__.py -------------------------------------------------------------------------------- /test/django_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /test/django_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for Angular_Django_REST_Resource_Test project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = '=eimp4enwyw-z5opxcmt$bvpycx0bu@mkb=-^0r+&iw+-)6xts' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 'rest_framework', 40 | 'test_rest', 41 | ) 42 | 43 | MIDDLEWARE_CLASSES = ( 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.messages.middleware.MessageMiddleware', 49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 50 | ) 51 | 52 | ROOT_URLCONF = 'urls' 53 | 54 | WSGI_APPLICATION = 'wsgi.application' 55 | 56 | 57 | # Database 58 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 59 | 60 | DATABASES = { 61 | 'default': { 62 | 'ENGINE': 'django.db.backends.sqlite3', 63 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 64 | } 65 | } 66 | 67 | # Internationalization 68 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 69 | 70 | LANGUAGE_CODE = 'en-us' 71 | 72 | TIME_ZONE = 'UTC' 73 | 74 | USE_I18N = True 75 | 76 | USE_L10N = True 77 | 78 | USE_TZ = True 79 | 80 | 81 | # Static files (CSS, JavaScript, Images) 82 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 83 | 84 | STATIC_URL = '/static/' 85 | 86 | REST_FRAMEWORK = { 87 | 'PAGINATE_BY': 5 88 | } -------------------------------------------------------------------------------- /test/django_project/test_rest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blacklocus/angular-django-rest-resource/22c0631e8af4a31cc013471a9e0f219d1c6f6b66/test/django_project/test_rest/__init__.py -------------------------------------------------------------------------------- /test/django_project/test_rest/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from test_rest.models import Plant, Animal 4 | 5 | # Register your models here. 6 | admin.site.register(Plant) 7 | admin.site.register(Animal) -------------------------------------------------------------------------------- /test/django_project/test_rest/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "test_rest.plant", 5 | "fields": { 6 | "common_name": "Venus Flytrap", 7 | "scientific_name": "Dionaea muscipula", 8 | "is_evergreen": false, 9 | "is_succulent": false 10 | } 11 | }, 12 | { 13 | "pk": 2, 14 | "model": "test_rest.plant", 15 | "fields": { 16 | "common_name": "Sensitive Plant", 17 | "scientific_name": "Mimosa pudica", 18 | "is_evergreen": false, 19 | "is_succulent": false 20 | } 21 | } 22 | ] -------------------------------------------------------------------------------- /test/django_project/test_rest/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Plant(models.Model): 5 | common_name = models.CharField(max_length=512) 6 | scientific_name = models.CharField(max_length=1024) 7 | is_succulent = models.NullBooleanField() 8 | is_evergreen = models.NullBooleanField() 9 | 10 | def __unicode__(self): 11 | return self.common_name 12 | 13 | 14 | class Animal(models.Model): 15 | common_name = models.CharField(max_length=512) 16 | scientific_name = models.CharField(max_length=1024) 17 | is_mammal = models.NullBooleanField() 18 | is_reptile = models.NullBooleanField() 19 | is_arachnid = models.NullBooleanField() 20 | 21 | def __unicode__(self): 22 | return self.common_name -------------------------------------------------------------------------------- /test/django_project/test_rest/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from test_rest.models import Animal 3 | from test_rest.models import Plant 4 | 5 | 6 | class AnimalSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Animal 9 | 10 | 11 | class PlantSerializer(serializers.ModelSerializer): 12 | class Meta: 13 | model = Plant 14 | -------------------------------------------------------------------------------- /test/django_project/test_rest/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Angular Django REST Resource Text 4 | 11 | 12 | 13 | 24 | 25 | 26 | 27 | Animals retrieved from the server so far: ({{ animals.length }}): 28 |
    29 |
  • {{animal.common_name}}
  • 30 |
31 | Plants retrieved from the server so far: ({{ plants.length }}): 32 |
    33 |
  • {{plant.common_name}}
  • 34 |
35 | 36 |
37 |
38 | Add a plant 39 | 40 | 41 | 42 |
43 |
44 | 45 |
46 |
47 | Edit a plant 48 | 49 | 50 | 51 |
52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /test/django_project/test_rest/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /test/django_project/test_rest/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets 2 | from test_rest.models import Animal, Plant 3 | from test_rest.serializers import AnimalSerializer, PlantSerializer 4 | 5 | 6 | class AnimalViewSet(viewsets.ModelViewSet): 7 | queryset = Animal.objects.all() 8 | serializer_class = AnimalSerializer 9 | 10 | 11 | class PlantViewSet(viewsets.ModelViewSet): 12 | queryset = Plant.objects.all() 13 | serializer_class = PlantSerializer -------------------------------------------------------------------------------- /test/django_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import patterns, include, url 3 | from django.views.generic.base import RedirectView 4 | from rest_framework import routers 5 | from test_rest import views 6 | from os import path 7 | 8 | from django.contrib import admin 9 | admin.autodiscover() 10 | 11 | rest_router = routers.DefaultRouter() 12 | rest_router.register(r'animals', views.AnimalViewSet) 13 | rest_router.register(r'plants', views.PlantViewSet) 14 | 15 | urlpatterns = patterns('', 16 | url(r'^$', RedirectView.as_view(url="/static/index.html")), 17 | url(r'^(?Pangular-django-rest-resource\.js)$', 'django.views.static.serve', { 18 | 'document_root': path.join(settings.BASE_DIR, ".."), 19 | }), 20 | url(r'^admin/', include(admin.site.urls)), 21 | url(r'^', include(rest_router.urls)), 22 | ) -------------------------------------------------------------------------------- /test/django_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for Angular_Django_REST_Resource_Test project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.6.5 2 | django-filter==0.7 3 | djangorestframework==2.3.13 4 | wsgiref==0.1.2 5 | --------------------------------------------------------------------------------