├── .coveragerc ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── djangular ├── __init__.py ├── config │ └── angularseed_template │ │ └── angular │ │ ├── app.css │ │ ├── app.js │ │ ├── components │ │ └── version │ │ │ ├── interpolate-filter.js │ │ │ ├── interpolate-filter_test.js │ │ │ ├── version-directive.js │ │ │ ├── version-directive_test.js │ │ │ ├── version.js │ │ │ └── version_test.js │ │ ├── index.html │ │ ├── view1 │ │ ├── view1.html │ │ ├── view1.js │ │ └── view1_test.js │ │ └── view2 │ │ ├── view2.html │ │ ├── view2.js │ │ └── view2_test.js ├── finders.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── startangularapp.py ├── middleware.py ├── models.py ├── static │ └── js │ │ └── resource_patch.js ├── storage.py ├── templates │ └── djangular_module.js ├── tests │ ├── __init__.py │ ├── test_base.py │ ├── test_commands.py │ ├── test_finders.py │ ├── test_middleware.py │ ├── test_storage.py │ ├── test_urls.py │ ├── test_utils.py │ └── unit │ │ └── filterSpec.js ├── urls.py ├── utils.py └── views.py ├── runtests.py └── setup.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | # Regexes for lines to exclude from consideration 3 | exclude_lines = 4 | # Have to re-enable the standard pragma 5 | pragma: no cover 6 | 7 | # Don't complain about missing debug-only code: 8 | def __repr__ 9 | if self\.debug 10 | def __unicode__ 11 | def __repr__ 12 | if settings.DEBUG 13 | raise NotImplementedError 14 | from django\. 15 | 16 | # Don't complain if tests don't hit defensive assertion code: 17 | raise AssertionError 18 | raise NotImplementedError 19 | 20 | # Don't complain if non-runnable code isn't run: 21 | if 0: 22 | if __name__ == .__main__.: 23 | 24 | [run] 25 | omit = 26 | *tests* 27 | *migrations* 28 | *site-packages* 29 | *src* 30 | *settings* 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pot 3 | *.pyc 4 | local_settings.py 5 | 6 | .DS_Store 7 | .idea 8 | .hg 9 | .hgignore 10 | 11 | dist 12 | djangular.egg-info 13 | 14 | .coverage 15 | 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: 5 | - pip install coverage 6 | - pip install $DJANGO 7 | script: 8 | - coverage run runtests.py 9 | - coverage report -m 10 | env: 11 | - DJANGO="Django==1.5.12" 12 | - DJANGO="Django==1.6.11" 13 | - DJANGO="Django==1.7.11" 14 | - DJANGO="Django==1.8.12" 15 | - DJANGO="Django==1.9.5" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Applied Security 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include djangular * 2 | include LICENSE 3 | include README.md 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | djangular 2 | ========= 3 | 4 | A reusable app that provides better app integration with AngularJS. Djangular 5 | allows you to create AngularJS content per app, instead of creating a single 6 | massive AngularJS application inside of Django. This allows you to selectively 7 | use apps per site, as well as create a consistent structure across all of your 8 | Django apps. 9 | 10 | This is intended to be a Django version of the angular-seed project 11 | (https://github.com/angular/angular-seed). The current mindset is to limit the 12 | amount of changes introduced by Djangular. 13 | 14 | 15 | Features 16 | -------- 17 | 18 | + Allows namespacing AngularJS content per Django app. This allows the 19 | AngularJS apps and modules to be included (or not) based on Django's settings, 20 | and enforces a consistent structure to your Django/AngularJS apps. 21 | + Includes an AngularJS module that includes a subset of features similar to 22 | what Django provides in its templates. 23 | + Adds a patch to AngularJS's $resource module, to enable end of URL slashes 24 | that Django requires. 25 | + Improves security by enabling of CSRF protection and JSON Vulnerability 26 | between Django and AngularJS. 27 | + ~~Scripts to allow running JS Unit and E2E tests, similar to the Django test 28 | command.~~ This was removed for the time being and will be (re-)included in a 29 | future release. 30 | + Does not dictate how you use AngularJS inside your Django app. 31 | 32 | 33 | Requirements 34 | ------------ 35 | 36 | + Currently requires Python 2.7. 37 | + Supports Django >= 1.5, <= 1.9 38 | + Supports AngularJS 1.2+ (including 1.3.x). 39 | + ~~Local installs of Node.js and Karma for testing.~~ 40 | 41 | 42 | Installation 43 | ------------ 44 | 45 | + You may install directly from pypi: 46 | 47 | pip install djangular 48 | 49 | + Or download the source and install it in a terminal/console: 50 | 51 | python setup.py install 52 | 53 | + Or download the source and move the djangular directory inside your django 54 | project as an app (this is the least recommended approach). 55 | 56 | + Djangular needs to be placed as an app inside a Django project and added to 57 | the INSTALLED_APPS setting. 58 | 59 | INSTALLED_APPS = ( 60 | ... 61 | 'djangular', 62 | ... 63 | ) 64 | 65 | + You will need to obtain a version of AngularJS and place it in the `static` 66 | folder of one of your Django apps. Djangular no longer includes a version of 67 | AngularJS, since it updates too frequently. 68 | 69 | 70 | Including AngularJS content in your Django Apps 71 | ----------------------------------------------- 72 | 73 | The most popular feature of Djangular, this will both include and namespace your 74 | AngularJS content inside your Django apps. Each Django app has its own 75 | "angular" folder, with a layout matching the angular-seed project. As a result, 76 | the URLs for these get grouped into the `STATIC_URL` structure of Django. 77 | 78 | + The staticfiles contrib library will need to be included in the INSTALLED_APPS 79 | setting. 80 | 81 | INSTALLED_APPS = ( 82 | ... 83 | 'django.contrib.staticfiles', 84 | 'djangular', 85 | ... 86 | ) 87 | 88 | + The STATICFILES_FINDERS needs to be updated to include 89 | `djangular.finders.NamespacedAngularAppDirectoriesFinder`. 90 | 91 | STATICFILES_FINDERS = ( 92 | 'django.contrib.staticfiles.finders.FileSystemFinder', 93 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 94 | 'djangular.finders.NamespacedAngularAppDirectoriesFinder' 95 | ) 96 | 97 | + Because of this new finder, the `findstatic` and `collectstatic` commands will 98 | place the angular files in each app in an associated an `/` folder. 99 | You will not need to namespace each of your static directories with the name 100 | of your Django application (unless you really want to). 101 | * Example: If you have a Django app named `foo` and you are using the 102 | default `STATIC_URL` in your settings, the main AngularJS module named 103 | `foo` would be found at `foo/angular/app.js` on the file system and at 104 | `static/foo/app.js` from the browser. 105 | * This namespacing is done automatically. This a `foo` app and a `bar` app 106 | can both have an `app.js` inside their `angular` directories, and they 107 | will not collide. 108 | * Note: Because of these URLs, referring to AngularJS content in a separate 109 | app should use a `..//` URL. This will help significantly 110 | during testing to make sure paths are correct. 111 | * Note: It is recommended to namespace the AngularJS code the same name as 112 | the Django app. The created JS files do this already. 113 | 114 | + To create an app that is already setup with the djangular (or angular-seed) 115 | structure, run `python manage.py startangularapp ` from the command 116 | line. This will create the files and directory structures needed for you to 117 | get started. 118 | 119 | 120 | Including some Django template-like features in your AngularJS templates 121 | ------------------------------------------------------------------------ 122 | 123 | One of the challenges in using AngularJS inside of Django is that you may not 124 | have access to some needed variables that are always included in Django 125 | templates. Djangular includes an AngularJS module to help with that. 126 | 127 | + To use the AngularJS module that Djangular provides you, you'll need to add 128 | the djangular app to your projects URLs. 129 | 130 | urlpatterns = patterns('', 131 | ... 132 | url(r'^djangular/', include('djangular.urls')), 133 | ... 134 | ) 135 | 136 | + Alternatively, you may specify the DjangularModuleTemplateView specifically, 137 | and customize the url. 138 | 139 | from djangular.views import DjangularModuleTemplateView 140 | ... 141 | 142 | urlpatterns = patterns('', 143 | ... 144 | url(r'/djangular.js', 145 | DjangularModuleTemplateView.as_view()), 146 | ... 147 | ) 148 | 149 | This will add a `djangular` AngularJS module to your front end code. This 150 | module includes a `DjangoProperties` constant that includes whether the current 151 | user is authenticated, the username, groups and roles of the current user and 152 | the static and media urls from Django settings. It also includes a `django` 153 | filter, which does some basic substitution based on the properties constant. 154 | 155 | 156 | Enforcing the end slashes of your AngularJS Resources 157 | ----------------------------------------------------- 158 | 159 | $resource is a convenient way to create REST-like services in AngularJS. 160 | However, there currently 161 | [is a bug](https://github.com/angular/angular.js/issues/992) in $resource that 162 | will strip the ending slash, which means that $resource is unusable unless 163 | `settings.APPEND_SLASHES` is set to `FALSE`. 164 | 165 | Djangular used to patch this automatically, but it now includes a separate file 166 | (`djangular/static/js/resource_patch.js`) to handle this issue. Simply include 167 | that javascript file in your page after you have loaded `angular-resource.js` 168 | and ending slashes will be preserved in $resource. 169 | 170 | 171 | Enabling CSRF protection in AngularJS Templates 172 | ----------------------------------------------- 173 | 174 | Djangular includes a JSON Vulnerability middleware that AngularJS knows how to 175 | process. To include this protection, add 176 | `djangular.middleware.AngularJsonVulnerabilityMiddleware` to the 177 | `MIDDLEWARE_CLASSES` setting. This only affects JSON requests (based on 178 | Content-Type), so this can be located fairly low in the middleware stack. 179 | 180 | MIDDLEWARE_CLASSES = ( 181 | ... 182 | 'djangular.middleware.AngularJsonVulnerabilityMiddleware' 183 | ) 184 | 185 | Once you have enabled CSRF protection in Django by adding the middleware 186 | `django.middleware.csrf.CsrfViewMiddleware` to the `MIDDLEWARE_CLASSES` setting, 187 | you may use the same protection in AngularJS templates in addition to Django 188 | template. There are two different ways to enable this protection via djangular: 189 | 190 | + Make your main app dependent on the `djangular` module and use the included 191 | `csrf-token` directive (that wraps the Django `csrf_token` template tag) 192 | inside the appropriate `form` tags in your HTML. 193 | 194 | // Inside your JavaScript 195 | angular.module('myApp', ['djangular', ...]); 196 | ... 197 | 198 |
199 | ... 200 |
201 | 202 |
203 |
204 | 205 | + Make your main app dependent on the `djangular.csrf`, which will add the 206 | appropriate CSRF Token Header in all POSTs, PUTs and DELETEs. Note that this 207 | way is vulnerable to cross-site scripting if you make a post to a domain 208 | outside your control. 209 | 210 | angular.module('myApp', ['djangular.csrf', ...]); 211 | 212 | If you allow a user to login (or logout) and don't redirect or reload the page, 213 | the tags and cookies provided by both methods above will be stale. The second 214 | option (using the `djangular.csrf` module) provides a `UpdateCSRFToken` function 215 | that can be invoked with the new CSRF Token value. 216 | 217 | 218 | Using Djangular in your Django Project 219 | -------------------------------------- 220 | 221 | This section describes some best practices in using Djangular. Please note that 222 | these are guidelines and recommendations, but not the only way to use this 223 | project. 224 | 225 | #### AngularJS as purely static content #### 226 | 227 | The first way to use djangular is to have all of your static content live inside 228 | an angular app. This is perhaps the "most correct" way from an AngularJS 229 | standpoint and perhaps the "least correct" way from a traditional Django 230 | perspective. 231 | 232 | In doing this, you are only (or almost only) serving content from your static 233 | domain and your Django development becomes strictly back-end focused for REST 234 | and/or service calls. Few to none of your Django views will produce HTML. You 235 | can provide a redirect from a Django view to your static pages (if you like), 236 | although this can seem strange when your static content is served from a 237 | completely different domain. You may need to configure your web servers to 238 | allow remote Ajax calls from your static domain. 239 | 240 | This approach allows you to use AngularJS with any number of back-ends, as 241 | (again) your Django app becomes an API for your AngularJS code to call. This 242 | approach can be very different from how your application is currently 243 | architected. 244 | 245 | From our experience, if you decide to do this, we would recommend using local, 246 | relative URLs to navigate between the apps instead of specifying the full URL. 247 | However, there are times when you will need to specify the full URL. 248 | 249 | There is an AngularJS module called `djangular` that is rendered via the Django 250 | templating engine to obtain common template variables like the `STATIC_URL`, 251 | `MEDIA_URL`, the User object, etc. This app includes a service called 252 | `DjangoProperties`, which will enable you to get access to those variables, and 253 | a `django` filter, which follows the standard AngularJS filtering rules. The URL 254 | for this JavaScript is `/djangular/app.js` (note that is not static). 255 | 256 | The following is a sample route config that uses the aforementioned djangular 257 | angular app. Because AngularJS has not set up the $filter directive during the 258 | route configuration, the DjangoProperties constant is the only way to obtain the 259 | STATIC_URL. Using 'sample' as the name of the Django/AngularJS app: 260 | 261 | ```javascript 262 | angular.module('sample', [ 263 | 'djangular', 'sample.filters', 'sample.services', 'sample.directives', 264 | 'sample.controllers' 265 | ]).config([ 266 | '$routeProvider','DjangoProperties', 267 | function($routeProvider, DjangoProperties) { 268 | $routeProvider.when('/view1', { 269 | templateUrl: DjangoProperties.STATIC_URL + 270 | 'sample/view1/view1.html', controller: 'View1Ctrl'}); 271 | $routeProvider.when('/view2', { 272 | templateUrl: DjangoProperties.STATIC_URL + 273 | 'sample/view2/view2.html', controller: 'View2Ctrl'}); 274 | $routeProvider.otherwise({redirectTo: '/view1'}); 275 | } 276 | ]); 277 | ``` 278 | 279 | #### Django Templates as AngularJS Templates #### 280 | 281 | Another way to integrate is to use your Django templates as your AngularJS 282 | templates. To do this, we highly recommend using Django 1.5 and heavy use of 283 | the `{% verbatim %}` tag, since the Django and AngularJS templating syntaxes are 284 | identical. 285 | 286 | The big advantage of this is that it allows you to use all of the existing 287 | template tags and ways of thinking that you are accustomed to using. If you are 288 | integrating AngularJS into an existing Django project, this will seem the most 289 | appealing. 290 | 291 | The downsides to this method are the following: 292 | + The AngularJS developers recommend not doing this, because it is very easy 293 | to get confused about which part of the template is being rendered on the 294 | server side and which is being rendered on the client side. Almost every 295 | developer on our team has tripped on this once or twice. 296 | + The vast majority of HTML that your app is producing is the same on every 297 | load... and should be static. However, without some cache configuration, the 298 | server will have to render the content on every single request, resulting in 299 | poorer performance. 300 | 301 | #### Using Django Templates to render the skeleton of the app #### 302 | 303 | What our team currently does is use a Django Template to render the skeleton of 304 | every page, but the rest of the page (the partials, CSS and JS) are all included 305 | in the AngularJS app. This way, none of the CSS/JS dependencies are duplicated 306 | in multiple places. 307 | 308 | When our app renders the content, we pass in two variables to the RequestContext 309 | (and thus, to the template). The `app_name`, which is the name of the app, and 310 | `app_dependencies`, which is a list of app names whom the AngularJS app is 311 | dependent on. We make heavy use of Django Rest Framework 312 | (http://django-rest-framework.org/) to produce our views/REST Services and 313 | Django Pipeline (https://github.com/cyberdelia/django-pipeline) to do our app 314 | packaging and JS/CSS Compression. 315 | 316 | The template (more or less) looks like the following: 317 | ```html 318 | {% load compressed %} 319 | 320 | 321 | 322 | 323 | 324 | App Title 325 | 326 | 327 | 328 | {% for dependency in app_dependencies %} 329 | {% compressed_css dependency %} 330 | {% endfor %} 331 | {% compressed_css app_name %} 332 | 333 | 334 | 335 | 336 | 337 | 338 | {% for dependency in app_dependencies %} 339 | {% compressed_js dependency %} 340 | {% endfor %} 341 | {% compressed_js app_name %} 342 | 343 | 344 | 345 |
346 |
347 | 348 | 349 | ``` -------------------------------------------------------------------------------- /djangular/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appliedsec/djangular/8d6bc26cd70b76962fa67d9be012e634047d1c61/djangular/__init__.py -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/app.css: -------------------------------------------------------------------------------- 1 | /* app css stylesheet */ 2 | 3 | .menu { 4 | list-style: none; 5 | border-bottom: 0.1em solid black; 6 | margin-bottom: 2em; 7 | padding: 0 0 0.5em; 8 | } 9 | 10 | .menu:before { 11 | content: "["; 12 | } 13 | 14 | .menu:after { 15 | content: "]"; 16 | } 17 | 18 | .menu > li { 19 | display: inline; 20 | } 21 | 22 | .menu > li:before { 23 | content: "|"; 24 | padding-right: 0.3em; 25 | } 26 | 27 | .menu > li:nth-child(1):before { 28 | content: ""; 29 | padding: 0; 30 | } 31 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Declare app level module which depends on views, and components 4 | angular.module('{{ app_name }}', [ 5 | 'ngRoute', 6 | '{{ app_name }}.view1', 7 | '{{ app_name }}.view2', 8 | '{{ app_name }}.version' 9 | ]). 10 | config(['$routeProvider', function($routeProvider) { 11 | $routeProvider.otherwise({redirectTo: '/view1'}); 12 | }]); 13 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/components/version/interpolate-filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('{{ app_name }}.version.interpolate-filter', []) 4 | 5 | .filter('interpolate', ['version', function(version) { 6 | return function(text) { 7 | return String(text).replace(/\%VERSION\%/mg, version); 8 | }; 9 | }]); 10 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/components/version/interpolate-filter_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('{{ app_name }}.version module', function() { 4 | beforeEach(module('{{ app_name }}.version')); 5 | 6 | describe('interpolate filter', function() { 7 | beforeEach(module(function($provide) { 8 | $provide.value('version', 'TEST_VER'); 9 | })); 10 | 11 | it('should replace VERSION', inject(function(interpolateFilter) { 12 | expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); 13 | })); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/components/version/version-directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('{{ app_name }}.version.version-directive', []) 4 | 5 | .directive('appVersion', ['version', function(version) { 6 | return function(scope, elm, attrs) { 7 | elm.text(version); 8 | }; 9 | }]); 10 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/components/version/version-directive_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('{{ app_name }}.version module', function() { 4 | beforeEach(module('{{ app_name }}.version')); 5 | 6 | describe('app-version directive', function() { 7 | it('should print current version', function() { 8 | module(function($provide) { 9 | $provide.value('version', 'TEST_VER'); 10 | }); 11 | inject(function($compile, $rootScope) { 12 | var element = $compile('')($rootScope); 13 | expect(element.text()).toEqual('TEST_VER'); 14 | }); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/components/version/version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('{{ app_name }}.version', [ 4 | '{{ app_name }}.version.interpolate-filter', 5 | '{{ app_name }}.version.version-directive' 6 | ]) 7 | 8 | .value('version', '0.1'); 9 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/components/version/version_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('{{ app_name }}.version module', function() { 4 | beforeEach(module('{{ app_name }}.version')); 5 | 6 | describe('version service', function() { 7 | it('should return current version', inject(function(version) { 8 | expect(version).toEqual('0.1'); 9 | })); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | My AngularJS App 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 26 | 27 |
28 | 29 |
Angular seed app: v
30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/view1/view1.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 1.

2 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/view1/view1.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('{{ app_name }}.view1', ['ngRoute']) 4 | 5 | .config(['$routeProvider', function($routeProvider) { 6 | $routeProvider.when('/view1', { 7 | templateUrl: 'view1/view1.html', 8 | controller: 'View1Ctrl' 9 | }); 10 | }]) 11 | 12 | .controller('View1Ctrl', [function() { 13 | 14 | }]); -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/view1/view1_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('{{ app_name }}.view1 module', function() { 4 | 5 | beforeEach(module('{{ app_name }}.view1')); 6 | 7 | describe('view1 controller', function(){ 8 | 9 | it('should ....', inject(function($controller) { 10 | //spec body 11 | var view1Ctrl = $controller('View1Ctrl'); 12 | expect(view1Ctrl).toBeDefined(); 13 | })); 14 | 15 | }); 16 | }); -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/view2/view2.html: -------------------------------------------------------------------------------- 1 | {% verbatim %} 2 |

This is the partial for view 2.

3 |

4 | Showing of 'interpolate' filter: 5 | {{ 'Current version is v%VERSION%.' | interpolate }} 6 |

7 | {% endverbatim %} 8 | -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/view2/view2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('{{ app_name }}.view2', ['ngRoute']) 4 | 5 | .config(['$routeProvider', function($routeProvider) { 6 | $routeProvider.when('/view2', { 7 | templateUrl: 'view2/view2.html', 8 | controller: 'View2Ctrl' 9 | }); 10 | }]) 11 | 12 | .controller('View2Ctrl', [function() { 13 | 14 | }]); -------------------------------------------------------------------------------- /djangular/config/angularseed_template/angular/view2/view2_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('{{ app_name }}.view2 module', function() { 4 | 5 | beforeEach(module('{{ app_name }}.view2')); 6 | 7 | describe('view2 controller', function(){ 8 | 9 | it('should ....', inject(function($controller) { 10 | //spec body 11 | var view2Ctrl = $controller('View2Ctrl'); 12 | expect(view2Ctrl).toBeDefined(); 13 | })); 14 | 15 | }); 16 | }); -------------------------------------------------------------------------------- /djangular/finders.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.contrib.staticfiles import finders as s_finders 3 | 4 | # Django rewrote the staticfiles storage internals in 1.7, so... 5 | if django.get_version() >= '1.7': 6 | import os 7 | import re 8 | 9 | class NamespacedAppDirectoriesFinder(s_finders.AppDirectoriesFinder): 10 | """ 11 | A namedspace static files finder that looks in the angular directory of 12 | each app as specified in the source_dir attribute. 13 | """ 14 | prepend_source_dir = False 15 | 16 | def __init__(self, app_names=None, *args, **kwargs): 17 | super(NamespacedAppDirectoriesFinder, self).__init__( 18 | app_names, *args, **kwargs) 19 | 20 | for app_name, storage in self.storages.items(): 21 | storage.prefix = os.path.join(*(app_name.split('.'))) 22 | 23 | def find_in_app(self, app, path): 24 | if self.prepend_source_dir: 25 | prefixed_path = os.path.join(self.source_dir, *(app.split('.'))) 26 | else: 27 | prefixed_path = os.path.join(*(app.split('.'))) 28 | 29 | app_re = '^{}{}'.format(prefixed_path, os.sep) 30 | if re.match(app_re, path): 31 | return super(NamespacedAppDirectoriesFinder, self).find_in_app( 32 | app, re.sub(app_re, '', path) 33 | ) 34 | 35 | class NamespacedAngularAppDirectoriesFinder(NamespacedAppDirectoriesFinder): 36 | """ 37 | A static files finder that looks in the angular directory of each app. 38 | """ 39 | source_dir = 'angular' 40 | 41 | class NamespacedE2ETestAppDirectoriesFinder(NamespacedAppDirectoriesFinder): 42 | """ 43 | A static files finder that looks in the tests/e2e directory of each app. 44 | """ 45 | source_dir = os.path.join('tests', 'e2e') 46 | prepend_source_dir = True 47 | 48 | else: 49 | from . import storage 50 | 51 | class NamespacedAngularAppDirectoriesFinder(s_finders.AppDirectoriesFinder): 52 | """ 53 | A static files finder that looks in the angular directory of each app. 54 | """ 55 | storage_class = storage.NamespacedAngularAppStorage 56 | 57 | class NamespacedE2ETestAppDirectoriesFinder(s_finders.AppDirectoriesFinder): 58 | """ 59 | A static files finder that looks in the tests/e2e directory of each app. 60 | """ 61 | storage_class = storage.NamespacedE2ETestAppStorage 62 | 63 | class NamespacedLibTestAppDirectoriesFinder(s_finders.AppDirectoriesFinder): 64 | """ 65 | A static files finder that looks in the tests/lib directory of each app. 66 | """ 67 | storage_class = storage.NamespacedLibTestAppStorage -------------------------------------------------------------------------------- /djangular/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appliedsec/djangular/8d6bc26cd70b76962fa67d9be012e634047d1c61/djangular/management/__init__.py -------------------------------------------------------------------------------- /djangular/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appliedsec/djangular/8d6bc26cd70b76962fa67d9be012e634047d1c61/djangular/management/commands/__init__.py -------------------------------------------------------------------------------- /djangular/management/commands/startangularapp.py: -------------------------------------------------------------------------------- 1 | import django 2 | import os 3 | 4 | from django.core import management as mgmt 5 | from django.core.management.templates import TemplateCommand 6 | from djangular import utils 7 | 8 | 9 | class Command(utils.SiteAndPathUtils, TemplateCommand): 10 | help = ("Creates a Djangular app directory structure for the given app " 11 | "name in the current directory or optionally in the given " 12 | "directory.") 13 | 14 | if django.get_version() >= "1.7": 15 | requires_system_checks = False 16 | else: 17 | requires_model_validation = False 18 | 19 | def handle(self, name, target=None, **options): 20 | mgmt.call_command('startapp', name, target, **options) 21 | 22 | # Override the options to setup the template command. 23 | options.update({ 24 | 'template': os.path.join( 25 | self.get_djangular_root(), 'config', 'angularseed_template'), 26 | 'extensions': ['.html', '.js'], # Parse HTML And JS files 27 | 'files': ['app.css'] 28 | }) 29 | 30 | super(Command, self).handle( 31 | 'app', name, target or name, **options) 32 | -------------------------------------------------------------------------------- /djangular/middleware.py: -------------------------------------------------------------------------------- 1 | 2 | class AngularJsonVulnerabilityMiddleware(object): 3 | """ 4 | A middleware that inserts the AngularJS JSON Vulnerability request on JSON responses. 5 | """ 6 | # The AngularJS JSON Vulnerability content prefix. See http://docs.angularjs.org/api/ng.$http 7 | CONTENT_PREFIX = b")]}',\n" 8 | 9 | # Make this class easy to extend by allowing class level access. 10 | VALID_STATUS_CODES = [200, 201, 202] 11 | VALID_CONTENT_TYPES = ['application/json'] 12 | 13 | def process_response(self, request, response): 14 | if response.status_code in self.VALID_STATUS_CODES and response['Content-Type'] in self.VALID_CONTENT_TYPES: 15 | response.content = self.CONTENT_PREFIX + response.content 16 | 17 | return response -------------------------------------------------------------------------------- /djangular/models.py: -------------------------------------------------------------------------------- 1 | # No models needed. -------------------------------------------------------------------------------- /djangular/static/js/resource_patch.js: -------------------------------------------------------------------------------- 1 | try { 2 | if (angular.version.full > "1.3") { 3 | angular.module('ngResource').config([ 4 | '$resourceProvider', function($resourceProvider) { 5 | $resourceProvider.defaults.stripTrailingSlashes = false; 6 | } 7 | ]); 8 | } else { 9 | angular.module('ngResource').config([ 10 | '$provide', '$httpProvider', 11 | function($provide, $httpProvider) { 12 | $provide.decorator('$resource', function($delegate) { 13 | return function() { 14 | if (arguments.length > 0) { // URL 15 | arguments[0] = arguments[0].replace(/\/$/, '\\/'); 16 | } 17 | 18 | if (arguments.length > 2) { // Actions 19 | angular.forEach(arguments[2], function(action) { 20 | if (action && action.url) { 21 | action.url = action.url.replace(/\/$/, '\\/'); 22 | } 23 | }); 24 | } 25 | 26 | return $delegate.apply($delegate, arguments); 27 | }; 28 | }); 29 | 30 | $provide.factory('djangularEnforceSlashInterceptor', function() { 31 | return { 32 | request: function(config) { 33 | config.url = config.url.replace(/[\/\\]+$/, '/'); 34 | return config; 35 | } 36 | }; 37 | }); 38 | 39 | $httpProvider.interceptors.push('djangularEnforceSlashInterceptor'); 40 | } 41 | ]); 42 | } 43 | } catch (err) { 44 | console.log('The ngResource module could not be found.'); 45 | } -------------------------------------------------------------------------------- /djangular/storage.py: -------------------------------------------------------------------------------- 1 | import django 2 | 3 | if django.get_version() < '1.7': 4 | import os 5 | from re import sub 6 | 7 | from django.contrib.staticfiles.storage import AppStaticStorage 8 | 9 | class NamespacedAngularAppStorage(AppStaticStorage): 10 | """ 11 | A file system storage backend that takes an app module and works 12 | for the ``app`` directory of it. The app module will be included 13 | in the url for the content. 14 | """ 15 | source_dir = 'angular' 16 | 17 | def __init__(self, app, *args, **kwargs): 18 | """ 19 | Returns a static file storage if available in the given app. 20 | """ 21 | # app is the actual app module 22 | self.prefix = os.path.join(*(app.split('.'))) 23 | super(NamespacedAngularAppStorage, self).__init__(app, *args, **kwargs) 24 | 25 | def path(self, name): 26 | name = sub('^' + self.prefix + os.sep.encode('string-escape'), '', name) 27 | return super(NamespacedAngularAppStorage, self).path(name) 28 | 29 | 30 | class NamespacedE2ETestAppStorage(AppStaticStorage): 31 | """ 32 | A file system storage backend that takes an app module and works 33 | for the ``tests/e2e`` directory of it. The app module will be included 34 | in the url for the content. NOTE: This should only be used for 35 | end-to-end testing. 36 | """ 37 | source_dir = os.path.join('tests', 'e2e') 38 | 39 | def __init__(self, app, *args, **kwargs): 40 | """ 41 | Returns a static file storage if available in the given app. 42 | """ 43 | # app is the actual app module 44 | prefix_args = [self.source_dir] + app.split('.') 45 | self.prefix = os.path.join(*prefix_args) 46 | super(NamespacedE2ETestAppStorage, self).__init__(app, *args, **kwargs) 47 | 48 | 49 | class NamespacedLibTestAppStorage(AppStaticStorage): 50 | """ 51 | A file system storage backend that takes an app module and works 52 | for the ``tests/lib`` directory of it. The app module will be included 53 | in the url for the content. NOTE: This should only be used for 54 | end-to-end testing. 55 | """ 56 | source_dir = os.path.join('tests', 'lib') 57 | 58 | def __init__(self, app, *args, **kwargs): 59 | """ 60 | Returns a static file storage if available in the given app. 61 | """ 62 | # app is the actual app module 63 | prefix_args = app.split('.') + ['lib'] 64 | self.prefix = os.path.join(*prefix_args) 65 | super(NamespacedLibTestAppStorage, self).__init__(app, *args, **kwargs) 66 | -------------------------------------------------------------------------------- /djangular/templates/djangular_module.js: -------------------------------------------------------------------------------- 1 | // All template syntax is commented so this can be loaded as a normal JS file. 2 | // {% load static %} 3 | var djangular = angular.module('djangular', []). 4 | constant('DjangoProperties', { 5 | 'STATIC_URL': '{% get_static_prefix %}', 6 | 'MEDIA_URL': '{% get_media_prefix %}', 7 | 'USER_NAME': '{{ user.username|escapejs }}', 8 | 'GROUP_NAMES': [ // {% for group in user.groups.all %} 9 | '{{ group.name|escapejs }}', // {% endfor %} 10 | ], 11 | 'IS_AUTHENTICATED': 'True' === '{{ user.is_authenticated|escapejs }}', 12 | 'IS_STAFF': 'True' === '{{ user.is_staff }}', 13 | 'IS_SUPERUSER': 'True' === '{{ user.is_superuser }}' 14 | }). 15 | filter('django', ['DjangoProperties', function(DjangoProperties) { 16 | return function(text) { 17 | for (var constant in DjangoProperties) { 18 | text = text.replace('%' + constant + '%', DjangoProperties[constant]); 19 | text = text.replace(constant, DjangoProperties[constant]); 20 | } 21 | return text; 22 | } 23 | }]). 24 | directive('djangoHref', ['$filter', function($filter) { 25 | return { 26 | restrict: 'A', 27 | priority: 99, // same as ng-href 28 | link: function(scope, elem, attrs) { 29 | attrs.$observe('djangoHref', function(value) { 30 | if (!value) return; 31 | attrs.$set('href', $filter('django')(value)); 32 | }); 33 | } 34 | }; 35 | }]). 36 | directive('djangoSrc', ['$filter', function($filter) { 37 | return { 38 | restrict: 'A', 39 | priority: 99, // same as ng-src 40 | link: function(scope, elem, attrs) { 41 | attrs.$observe('djangoSrc', function(value) { 42 | if (!value) return; 43 | attrs.$set('src', $filter('django')(value)); 44 | }); 45 | } 46 | }; 47 | }]). 48 | directive('csrfToken', function() { 49 | return { 50 | restrict: 'E', 51 | template: "{% csrf_token %}" || "", 52 | replace: true 53 | }; 54 | }); 55 | 56 | // {% if not disable_csrf_headers %} 57 | // Assign the CSRF Token as needed, until Angular provides a way to do this properly (https://github.com/angular/angular.js/issues/735) 58 | var djangularCsrf = angular.module('djangular.csrf', ['ngCookies']). 59 | config(['$httpProvider', function($httpProvider) { 60 | // cache $httpProvider, as it's only available during config... 61 | djangularCsrf.$httpProvider = $httpProvider; 62 | }]). 63 | factory('UpdateCsrfToken', function() { 64 | return function(csrfToken) { 65 | djangularCsrf.$httpProvider.defaults.headers.post['X-CSRFToken'] = csrfToken; 66 | djangularCsrf.$httpProvider.defaults.headers.put['X-CSRFToken'] = csrfToken; 67 | if (!djangularCsrf.$httpProvider.defaults.headers.delete) 68 | djangularCsrf.$httpProvider.defaults.headers.delete = {}; 69 | djangularCsrf.$httpProvider.defaults.headers.delete['X-CSRFToken'] = csrfToken; 70 | }; 71 | }). 72 | run(['$cookies', 'UpdateCsrfToken', function($cookies, UpdateCsrfToken) { 73 | UpdateCsrfToken($cookies['csrftoken']); 74 | }]); 75 | // {% endif %} 76 | -------------------------------------------------------------------------------- /djangular/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import django 2 | 3 | if django.VERSION < (1, 6): 4 | from djangular.tests.test_base import * 5 | from djangular.tests.test_finders import * 6 | from djangular.tests.test_middleware import * 7 | from djangular.tests.test_storage import * 8 | from djangular.tests.test_utils import * 9 | from djangular.tests.test_commands import * 10 | from djangular.tests.test_urls import * 11 | -------------------------------------------------------------------------------- /djangular/tests/test_base.py: -------------------------------------------------------------------------------- 1 | import django 2 | import os 3 | 4 | from django.test import SimpleTestCase 5 | 6 | BASE_DIR = os.path.abspath(os.path.dirname(__file__)) 7 | 8 | 9 | def _call_test_func(self, test_fn): 10 | apps = None 11 | need_to_call_unset = False 12 | 13 | if django.get_version() >= '1.7': 14 | from django.apps import apps 15 | 16 | if not apps.is_installed('djangular.config.angularseed_template'): 17 | apps.set_installed_apps(tuple([ 18 | 'djangular.config.angularseed_template'])) 19 | need_to_call_unset = True 20 | 21 | try: 22 | test_fn(self) 23 | finally: 24 | if apps and need_to_call_unset: 25 | apps.unset_installed_apps() 26 | 27 | 28 | def test_with_angularseed_template_as_django_app(test_fn): 29 | def fn(self): 30 | extra_init_py_files = [] 31 | try: 32 | # Temporarily make the template dirs into python modules by adding 33 | # the __init__.py files. 34 | for directory in [ 35 | 'config', os.path.join('config', 'angularseed_template')]: 36 | current_file_name = os.path.join( 37 | BASE_DIR, '..', directory, '__init__.py') 38 | current_file = open(current_file_name, 'w') 39 | extra_init_py_files.append(current_file) 40 | current_file.close() 41 | 42 | except Exception as e: 43 | self.fail('Could not create files due to {0}'.format(e.message)) 44 | 45 | else: 46 | _call_test_func(self, test_fn) 47 | 48 | finally: 49 | for py_file in extra_init_py_files: 50 | if os.path.exists(py_file.name): 51 | os.remove(py_file.name) 52 | 53 | compiled_file_name = '{0}c'.format(py_file.name) 54 | if os.path.exists(compiled_file_name): 55 | os.remove(compiled_file_name) 56 | 57 | return fn 58 | 59 | 60 | class TestAngularSeedAsPythonModuleTest(SimpleTestCase): 61 | 62 | @test_with_angularseed_template_as_django_app 63 | def test_init_py_created(self): 64 | self.assertTrue(os.path.exists('{0}/../config/__init__.py'.format(BASE_DIR))) -------------------------------------------------------------------------------- /djangular/tests/test_commands.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from django.test import TestCase 5 | from django.utils._os import upath 6 | 7 | from djangular.management.commands.startangularapp import Command as StartAngularAppCommand 8 | 9 | 10 | class StartAngularAppCommandTests(TestCase): 11 | def setUp(self): 12 | # Clean up app directory that is created 13 | test_dir = os.path.abspath(os.path.dirname(upath(__file__))) 14 | demo_app_path = os.path.join(test_dir, '../../demo') 15 | self.addCleanup(shutil.rmtree, demo_app_path) 16 | 17 | def test_runs(self): 18 | StartAngularAppCommand().handle('demo', verbosity=1) 19 | -------------------------------------------------------------------------------- /djangular/tests/test_finders.py: -------------------------------------------------------------------------------- 1 | import django 2 | import os 3 | 4 | from djangular.tests.test_base import test_with_angularseed_template_as_django_app 5 | from djangular import finders 6 | from django.test import SimpleTestCase 7 | 8 | APP_BASE_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) 9 | 10 | 11 | class NamespacedAngularAppDirectoriesFinderTest(SimpleTestCase): 12 | @test_with_angularseed_template_as_django_app 13 | def test_find(self): 14 | if django.get_version() >= '1.7': 15 | finder = finders.NamespacedAngularAppDirectoriesFinder( 16 | app_names=['djangular.config.angularseed_template']) 17 | else: 18 | finder = finders.NamespacedAngularAppDirectoriesFinder( 19 | apps=['djangular.config.angularseed_template']) 20 | 21 | self.assertEqual( 22 | finder.find('djangular/config/angularseed_template/index.html'), 23 | '{0}/config/angularseed_template/angular/index.html'.format( 24 | APP_BASE_DIR) 25 | ) 26 | 27 | 28 | class NamespacedE2ETestAppDirectoriesFinderTest(SimpleTestCase): 29 | 30 | @test_with_angularseed_template_as_django_app 31 | def test_find(self): 32 | self.skipTest('E2E Testing is not implemented yet...') 33 | 34 | if django.get_version() >= '1.7': 35 | finder = finders.NamespacedE2ETestAppDirectoriesFinder( 36 | app_names=['djangular.config.angularseed_template']) 37 | else: 38 | finder = finders.NamespacedE2ETestAppDirectoriesFinder( 39 | apps=['djangular.config.angularseed_template']) 40 | 41 | self.assertEqual( 42 | finder.find( 43 | 'tests/e2e/djangular/config/angularseed_template/runner.html'), 44 | '{0}/config/angularseed_template/tests/e2e/runner.html'.format( 45 | APP_BASE_DIR) 46 | ) 47 | -------------------------------------------------------------------------------- /djangular/tests/test_middleware.py: -------------------------------------------------------------------------------- 1 | from djangular import middleware 2 | 3 | from django.test import SimpleTestCase 4 | from django.http import HttpRequest, HttpResponse 5 | 6 | 7 | class AngularJsonVulnerabilityMiddlewareTest(SimpleTestCase): 8 | 9 | def test_that_middleware_does_nothing_to_html_requests(self): 10 | resp = HttpResponse(content_type='text/html', content='') 11 | mware = middleware.AngularJsonVulnerabilityMiddleware() 12 | mware.process_response(HttpRequest(), resp) 13 | 14 | self.assertEqual(resp.content, '') 15 | 16 | def test_that_middleware_does_nothing_to_js_requests(self): 17 | resp = HttpResponse(content_type='text/javascript', content='var blah = [];') 18 | mware = middleware.AngularJsonVulnerabilityMiddleware() 19 | mware.process_response(HttpRequest(), resp) 20 | 21 | self.assertEqual(resp.content, 'var blah = [];') 22 | 23 | def test_that_middleware_does_nothing_to_invalid_json_requests(self): 24 | resp = HttpResponse(content_type='application/json', content='[1, 2, 3]', status=400) 25 | mware = middleware.AngularJsonVulnerabilityMiddleware() 26 | mware.process_response(HttpRequest(), resp) 27 | 28 | self.assertEqual(resp.content, '[1, 2, 3]') 29 | 30 | def test_that_middleware_adds_prefix_to_valid_json_requests(self): 31 | resp = HttpResponse(content_type='application/json', content='[1, 2, 3]') 32 | mware = middleware.AngularJsonVulnerabilityMiddleware() 33 | mware.process_response(HttpRequest(), resp) 34 | 35 | self.assertEqual(resp.content, mware.CONTENT_PREFIX + '[1, 2, 3]') 36 | -------------------------------------------------------------------------------- /djangular/tests/test_storage.py: -------------------------------------------------------------------------------- 1 | import django 2 | 3 | if django.get_version() < '1.7': 4 | from djangular import storage 5 | from django.test import SimpleTestCase 6 | 7 | from djangular.tests.test_base import test_with_angularseed_template_as_django_app 8 | 9 | class NamespacedAppAngularStorageTest(SimpleTestCase): 10 | def test_source_dir_is_angular(self): 11 | self.assertEqual( 12 | storage.NamespacedAngularAppStorage.source_dir, 'angular') 13 | 14 | def test_prefix_is_given_app_name(self): 15 | app_storage = storage.NamespacedAngularAppStorage('djangular') 16 | self.assertEqual(app_storage.prefix, 'djangular') 17 | 18 | @test_with_angularseed_template_as_django_app 19 | def test_prefix_is_given_app_name_for_more_complicated_scenario(self): 20 | app_storage = storage.NamespacedAngularAppStorage( 21 | 'djangular.config.angularseed_template') 22 | self.assertEqual(app_storage.prefix, 23 | 'djangular/config/angularseed_template') 24 | 25 | 26 | class NamespacedE2ETestAppStorageTest(SimpleTestCase): 27 | def test_source_dir_is_tests(self): 28 | self.assertEqual( 29 | storage.NamespacedE2ETestAppStorage.source_dir, 'tests/e2e') 30 | 31 | def test_prefix_is_given_app_name(self): 32 | app_storage = storage.NamespacedE2ETestAppStorage('djangular') 33 | self.assertEqual(app_storage.prefix, 'tests/e2e/djangular') 34 | 35 | @test_with_angularseed_template_as_django_app 36 | def test_prefix_is_given_app_name_for_more_complicated_scenario(self): 37 | app_storage = storage.NamespacedE2ETestAppStorage( 38 | 'djangular.config.angularseed_template') 39 | self.assertEqual(app_storage.prefix, 40 | 'tests/e2e/djangular/config/angularseed_template') -------------------------------------------------------------------------------- /djangular/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.test import TestCase 3 | 4 | 5 | class UrlsTests(TestCase): 6 | def test_urls_import(self): 7 | """Smoke test to make sure urls imports are valid.""" 8 | self.assertEqual('/app.js', reverse('djangular-module')) 9 | -------------------------------------------------------------------------------- /djangular/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from djangular import utils 4 | from django.test import SimpleTestCase 5 | 6 | 7 | class SiteAndPathUtilsTest(SimpleTestCase): 8 | 9 | site_utils = utils.SiteAndPathUtils() 10 | 11 | def test_djangular_root(self): 12 | current_dir = os.path.dirname(os.path.abspath(__file__)) 13 | djangular_dir = os.path.dirname(current_dir) 14 | self.assertEqual(djangular_dir, self.site_utils.get_djangular_root()) 15 | -------------------------------------------------------------------------------- /djangular/tests/unit/filterSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for filters go here */ 4 | 5 | describe('filter', function() { 6 | beforeEach(module('djangular')); 7 | 8 | describe('django', function() { 9 | it('should replace STATIC_URL inside of percent signs', inject(function(djangoFilter) { 10 | expect(djangoFilter('before %STATIC_URL% after')). 11 | toEqual('before {% get_static_prefix %} after'); 12 | })); 13 | 14 | it('should replace STATIC_URL without percent signs', inject(function(djangoFilter) { 15 | expect(djangoFilter('before STATIC_URL after')). 16 | toEqual('before {% get_static_prefix %} after'); 17 | })); 18 | 19 | it('should replace MEDIA_URL inside of percent signs', inject(function(djangoFilter) { 20 | expect(djangoFilter('before %MEDIA_URL% after')). 21 | toEqual('before {% get_media_prefix %} after'); 22 | })); 23 | 24 | it('should replace MEDIA_URL without percent signs', inject(function(djangoFilter) { 25 | expect(djangoFilter('before MEDIA_URL after')). 26 | toEqual('before {% get_media_prefix %} after'); 27 | })); 28 | 29 | it('should replace USER_NAME inside of percent signs', inject(function(djangoFilter) { 30 | expect(djangoFilter('before %USER_NAME% after')). 31 | toEqual('before {{ user.username|escapejs }} after'); 32 | })); 33 | 34 | it('should replace USER_NAME without percent signs', inject(function(djangoFilter) { 35 | expect(djangoFilter('before USER_NAME after')). 36 | toEqual('before {{ user.username|escapejs }} after'); 37 | })); 38 | 39 | it('should replace IS_AUTHENTICATED inside of percent signs', inject(function(djangoFilter) { 40 | expect(djangoFilter('before %IS_AUTHENTICATED% after')). 41 | toEqual('before false after'); 42 | })); 43 | 44 | it('should replace IS_AUTHENTICATED without percent signs', inject(function(djangoFilter) { 45 | expect(djangoFilter('before IS_AUTHENTICATED after')). 46 | toEqual('before false after'); 47 | })); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /djangular/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | from .views import DjangularModuleTemplateView 3 | 4 | urlpatterns = patterns('', 5 | url(r'^app.js$', DjangularModuleTemplateView.as_view(), name='djangular-module') 6 | ) 7 | -------------------------------------------------------------------------------- /djangular/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 4 | 5 | 6 | class SiteAndPathUtils(object): 7 | """ 8 | Mixin to get commonly used directories in Djangular Commands 9 | """ 10 | def get_default_site_app(self): 11 | """ 12 | Retrieves the name of the django app that contains the site config. 13 | """ 14 | return os.environ["DJANGO_SETTINGS_MODULE"].replace('.settings', '') 15 | 16 | def get_default_site_path(self): 17 | """ 18 | Retrieves the name of the django app that contains the site config. 19 | """ 20 | settings_module = __import__(self.get_default_site_app()) 21 | return settings_module.__path__[0] 22 | 23 | def get_djangular_root(self): 24 | """ 25 | Returns the absolute path of the djangular app. 26 | """ 27 | return CURRENT_DIR 28 | 29 | def get_project_root(self): 30 | """ 31 | Retrieves the root of the project directory without having to have a entry in the settings. 32 | """ 33 | default_site = self.get_default_site_app() 34 | path = self.get_default_site_path() 35 | # Move up one directory per '.' in site path. Most sites are at the top level, so this is just a precaution. 36 | for _ in range(len(default_site.split('.'))): 37 | path = os.path.dirname(path) 38 | return path 39 | -------------------------------------------------------------------------------- /djangular/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic.base import TemplateView 2 | 3 | 4 | class DjangularModuleTemplateView(TemplateView): 5 | content_type = 'text/javascript' 6 | template_name = 'djangular_module.js' 7 | disable_csrf_headers = False 8 | 9 | def get_context_data(self, **kwargs): 10 | context = super(DjangularModuleTemplateView, self).get_context_data(**kwargs) 11 | context['disable_csrf_headers'] = self.disable_csrf_headers 12 | return context 13 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | import django 5 | 6 | from django.conf import settings 7 | from django.core.management import execute_from_command_line 8 | 9 | 10 | if not settings.configured: 11 | settings.configure( 12 | DATABASES={ 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', 15 | } 16 | }, 17 | INSTALLED_APPS=[ 18 | 'django.contrib.auth', 19 | 'django.contrib.contenttypes', 20 | 'django.contrib.sessions', 21 | 'django.contrib.messages', 22 | 'django.contrib.sites', 23 | 'djangular', 24 | ], 25 | MIDDLEWARE_CLASSES=[ 26 | 'django.middleware.common.CommonMiddleware', 27 | 'django.contrib.sessions.middleware.SessionMiddleware', 28 | 'django.middleware.csrf.CsrfViewMiddleware', 29 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 30 | 'django.contrib.messages.middleware.MessageMiddleware', 31 | ], 32 | ROOT_URLCONF='djangular.urls' 33 | ) 34 | 35 | 36 | import logging 37 | logging.basicConfig( 38 | level = logging.DEBUG, 39 | format = '%(asctime)s %(levelname)s %(message)s', 40 | ) 41 | logging.disable(logging.CRITICAL) 42 | 43 | 44 | def runtests(): 45 | argv = sys.argv[:1] + ['test', 'djangular'] 46 | execute_from_command_line(argv) 47 | 48 | 49 | if __name__ == '__main__': 50 | runtests() 51 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup, find_packages 3 | except ImportError: 4 | from distribute_setup import use_setuptools 5 | use_setuptools() 6 | from setuptools import setup, find_packages 7 | 8 | setup( 9 | name='djangular', 10 | version='0.3.0b1', 11 | description="A reusable app that provides better app integration with AngularJS.", 12 | long_description=""" 13 | A reusable app that provides better app integration with AngularJS. Djangular 14 | allows you to create AngularJS content per app, instead of creating a single 15 | massive AngularJS application inside of Django. This allows you to selectively 16 | use apps per site, as well as create a consistent structure across all of your 17 | Django apps. 18 | 19 | This is intended to be a Django version of the angular-seed project 20 | (https://github.com/angular/angular-seed). The current mindset is to limit the 21 | amount of changes introduced by Djangular. 22 | """, 23 | keywords='djangular django angular angularjs', 24 | license='Apache', 25 | packages=['djangular'], 26 | include_package_data=True, 27 | author='Brian Montgomery', 28 | author_email='brianm@appliedsec.com', 29 | url='http://github.com/appliedsec/djangular', 30 | classifiers=[ 31 | "Intended Audience :: Developers", 32 | "License :: OSI Approved :: Apache Software License", 33 | "Programming Language :: Python :: 2.7", 34 | "Operating System :: OS Independent", 35 | "Topic :: Internet :: WWW/HTTP", 36 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 37 | "Topic :: Software Development :: Libraries :: Python Modules", 38 | ], 39 | ) 40 | --------------------------------------------------------------------------------