├── data ├── __init__.py ├── dumpdata.sh ├── import_topics.sql ├── empty_topic_data.sql ├── refresh.sh ├── make_unique.py ├── fulltext.sql └── holygrail.yaml ├── sbirez ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── modules │ │ └── splitting.py │ │ ├── indextopics.py │ │ ├── importtopics.py │ │ ├── readworkflow.py │ │ └── getnaics.py ├── migrations │ ├── __init__.py │ ├── 0027_merge.py │ ├── 0034_merge.py │ ├── 0023_merge.py │ ├── 0026_merge.py │ ├── 0031_merge.py │ ├── 0033_merge.py │ ├── 0018_merge.py │ ├── 0021_auto_20150420_2018.py │ ├── 0002_auto_20150219_1951.py │ ├── 0029_auto_20150804_2055.py │ ├── 0030_element_report_text.py │ ├── 0032_element_report_question_number.py │ ├── 0026_auto_20150720_2156.py │ ├── 0003_auto_20150219_1959.py │ ├── 0019_auto_20150406_0018.py │ ├── 0016_auto_20150331_1737.py │ ├── 0033_auto_20150910_2059.py │ ├── 0027_auto_20150724_2048.py │ ├── 0028_auto_20150724_2049.py │ ├── 0022_auto_20150420_2019.py │ ├── 0012_auto_20150317_1646.py │ ├── 0021_auto_20150420_2048.py │ ├── 0024_auto_20150612_1611.py │ ├── 0017_auto_20150403_1944.py │ ├── 0004_auto_20150223_2159.py │ ├── 0007_auto_20150303_1801.py │ ├── 0008_auto_20150304_2134.py │ ├── 0013_auto_20150317_1907.py │ ├── 0030_auto_20150817_1927.py │ ├── 0020_auto_20150414_1920.py │ ├── 0015_auto_20150326_1735.py │ ├── 0006_auto_20150302_2037.py │ ├── 0032_auto_20150910_0836.py │ ├── 0015_auto_20150327_1426.py │ ├── 0031_auto_20150828_1751.py │ ├── 0009_auto_20150313_1659.py │ ├── 0011_auto_20150317_1630.py │ ├── 0005_auto_20150225_1935.py │ └── 0014_auto_20150319_1730.py ├── media │ └── deathstarplans.txt ├── static │ ├── js │ │ ├── README │ │ ├── controllers │ │ │ ├── proposal.js │ │ │ ├── landing.js │ │ │ ├── contact.js │ │ │ ├── history.js │ │ │ ├── notification.js │ │ │ ├── adminuser.js │ │ │ ├── documentList.js │ │ │ ├── savedOpps.js │ │ │ ├── reset.js │ │ │ ├── form.js │ │ │ ├── main.js │ │ │ ├── accountUser.js │ │ │ ├── topic.js │ │ │ ├── document.js │ │ │ ├── search.js │ │ │ ├── signin.js │ │ │ └── accountOrganization.js │ │ ├── directives │ │ │ ├── footer.js │ │ │ ├── backbutton.js │ │ │ ├── elements │ │ │ │ ├── group.js │ │ │ │ ├── lineitem.js │ │ │ │ ├── readonlytext.js │ │ │ │ ├── singlelineitem.js │ │ │ │ ├── dynamiclineitem.js │ │ │ │ ├── bool.js │ │ │ │ ├── checkbox.js │ │ │ │ ├── text.js │ │ │ │ ├── calculated.js │ │ │ │ ├── str.js │ │ │ │ └── upload.js │ │ │ ├── topic.js │ │ │ ├── pagination.js │ │ │ └── jargon.js │ │ ├── services │ │ │ ├── authsvc.js │ │ │ ├── dialogsvc.js │ │ │ └── usersvc.js │ │ └── filters │ │ │ └── truncate.js │ ├── views │ │ └── partials │ │ │ ├── proposal.html │ │ │ ├── document.html │ │ │ ├── footer.html │ │ │ ├── appmain.html │ │ │ ├── elements │ │ │ ├── group.html │ │ │ ├── readonlytext.html │ │ │ ├── calculated.html │ │ │ ├── text.html │ │ │ ├── checkbox.html │ │ │ ├── str.html │ │ │ ├── bool.html │ │ │ ├── upload.html │ │ │ ├── singlelineitem.html │ │ │ ├── lineitem.html │ │ │ └── dynamiclineitem.html │ │ │ ├── history.html │ │ │ ├── proposal.details.html │ │ │ ├── reset.html │ │ │ ├── landing.html │ │ │ ├── main.html │ │ │ ├── header.html │ │ │ ├── postsignup.html │ │ │ ├── savedOpps.html │ │ │ ├── documentList.html │ │ │ ├── signin.html │ │ │ ├── topic.html │ │ │ ├── documentDetails.html │ │ │ ├── introMessage.html │ │ │ ├── search.html │ │ │ ├── accountUser.html │ │ │ ├── signup.html │ │ │ └── topicDetails.html │ ├── coverpage.pdf │ ├── images │ │ ├── png │ │ │ ├── cross.png │ │ │ ├── logo.png │ │ │ ├── search.png │ │ │ ├── checkmark.png │ │ │ ├── cross_red.png │ │ │ ├── checkmark_green.png │ │ │ └── checkbox-unchecked.png │ │ ├── svg │ │ │ ├── checkmark.svg │ │ │ ├── checkmark_green.svg │ │ │ ├── search.svg │ │ │ ├── cross.svg │ │ │ ├── cross_red.svg │ │ │ └── logo.svg │ │ └── icons.fallback.css │ └── css │ │ ├── ngdialog-theme-intromessage.css │ │ ├── ngdialog-theme-logout.css │ │ ├── ngdialog-theme-login.css │ │ └── reset.css ├── admin.py ├── templates │ ├── emails │ │ ├── submit_notification-subject.html │ │ ├── mock_submission-subject.html │ │ ├── submit_notification-body-text.html │ │ └── mock_submission-body-text.html │ ├── password_reset_confirm.html │ └── fragments │ │ └── password_reset_confirm_form.html ├── views.py ├── tests.py ├── templatetags │ ├── stringyesno.py │ └── money.py ├── fixtures │ ├── solicitation.json │ └── naics.json ├── permissions.py └── assets.py ├── Aptfile ├── afsbirez ├── __init__.py ├── settings │ ├── __init__.py │ ├── dev.py │ └── production.py ├── wsgi.py └── urls.py ├── chef ├── data_bags │ └── .gitkeep ├── roles │ ├── .gitkeep │ └── vagrant.rb ├── environments │ └── .gitkeep ├── nodes │ └── localhost.json ├── Gemfile ├── solo.rb └── Berksfile ├── .gitattributes ├── runtime.txt ├── requirements └── dev.txt ├── .bowerrc ├── .buildpacks ├── tests └── client │ ├── runner.html │ ├── .jshintrc │ └── spec │ ├── controllers │ ├── main.js │ ├── accountUser.js │ ├── account.js │ ├── documentList.js │ ├── proposal.js │ ├── topic.js │ ├── reset.js │ ├── savedopps.js │ └── accountOrganization.js │ └── services │ └── usersvc.js ├── manage.py ├── package.json ├── generate_fixtures.sh ├── .jshintrc ├── .editorconfig ├── .gitignore ├── .cfignore ├── manifest.yml ├── requirements.txt ├── bower.json ├── CONTRIBUTING.md ├── LICENSE.md ├── karma-e2e.conf.js ├── karma.conf.js └── .about.yml /data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbirez/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Aptfile: -------------------------------------------------------------------------------- 1 | mdbtools 2 | -------------------------------------------------------------------------------- /afsbirez/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chef/data_bags/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chef/roles/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /chef/environments/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.4.3 2 | -------------------------------------------------------------------------------- /afsbirez/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbirez/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbirez/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbirez/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbirez/media/deathstarplans.txt: -------------------------------------------------------------------------------- 1 | Don't shoot the exhaust port! -------------------------------------------------------------------------------- /sbirez/static/js/README: -------------------------------------------------------------------------------- 1 | Place your javascript files here. 2 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/proposal.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /sbirez/static/views/partials/document.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /chef/nodes/localhost.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_list": ["role[vagrant]"] 3 | } 4 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/footer.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /data/dumpdata.sh: -------------------------------------------------------------------------------- 1 | ./manage.py dumpdata sbirez > sbirez/fixtures/alldata.json 2 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | django-extensions==1.5.2 2 | django-debug-toolbar==1.2.2 3 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/appmain.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "sbirez/static/lib", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /sbirez/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /sbirez/templates/emails/submit_notification-subject.html: -------------------------------------------------------------------------------- 1 | Your SBIR proposal has been submitted -------------------------------------------------------------------------------- /chef/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'knife-solo' 4 | gem 'berkshelf' 5 | -------------------------------------------------------------------------------- /sbirez/templates/emails/mock_submission-subject.html: -------------------------------------------------------------------------------- 1 | SBIR proposal submission: {{proposal.title}} 2 | -------------------------------------------------------------------------------- /sbirez/static/coverpage.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/afsbirez/master/sbirez/static/coverpage.pdf -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/group.html: -------------------------------------------------------------------------------- 1 | {{element.human}} 2 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/history.html: -------------------------------------------------------------------------------- 1 |
2 |

History!

3 |
4 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/proposal.details.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /sbirez/static/images/png/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/afsbirez/master/sbirez/static/images/png/cross.png -------------------------------------------------------------------------------- /sbirez/static/images/png/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/afsbirez/master/sbirez/static/images/png/logo.png -------------------------------------------------------------------------------- /sbirez/static/images/png/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/afsbirez/master/sbirez/static/images/png/search.png -------------------------------------------------------------------------------- /sbirez/static/images/png/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/afsbirez/master/sbirez/static/images/png/checkmark.png -------------------------------------------------------------------------------- /sbirez/static/images/png/cross_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/afsbirez/master/sbirez/static/images/png/cross_red.png -------------------------------------------------------------------------------- /.buildpacks: -------------------------------------------------------------------------------- 1 | https://github.com/ddollar/heroku-buildpack-apt.git 2 | https://github.com/cloudfoundry/buildpack-python.git#v1.3.5 3 | -------------------------------------------------------------------------------- /sbirez/static/images/png/checkmark_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/afsbirez/master/sbirez/static/images/png/checkmark_green.png -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/readonlytext.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |

4 | -------------------------------------------------------------------------------- /sbirez/static/images/png/checkbox-unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/afsbirez/master/sbirez/static/images/png/checkbox-unchecked.png -------------------------------------------------------------------------------- /sbirez/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | def home(request): 5 | return render(request, 'index.html') 6 | -------------------------------------------------------------------------------- /data/import_topics.sql: -------------------------------------------------------------------------------- 1 | drop schema if exists import cascade; 2 | create schema import; 3 | set search_path=import; 4 | \i topics.sql 5 | \i transfer_schemas.sql 6 | \i fulltext.sql -------------------------------------------------------------------------------- /chef/solo.rb: -------------------------------------------------------------------------------- 1 | root = File.absolute_path(File.dirname(__FILE__)) 2 | 3 | file_cache_path root 4 | cookbook_path [root + '/cookbooks', root + '/site-cookbooks'] 5 | role_path root + '/roles' 6 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/proposal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('ProposalCtrl', function ($scope, $state) { 5 | $scope.proposalId = $state.params.id; 6 | }); -------------------------------------------------------------------------------- /sbirez/tests.py: -------------------------------------------------------------------------------- 1 | import doctest 2 | import sbirez.validation_helpers 3 | 4 | def load_tests(loader, tests, ignore): 5 | tests.addTests(doctest.DocTestSuite(sbirez.validation_helpers)) 6 | return tests 7 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/landing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('LandingCtrl', function ($scope, $rootScope) { 5 | $rootScope.bodyClass = 'sign-up-post'; 6 | }); 7 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/contact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('ContactCtrl', [ '$scope', function ContactCtrl($scope) { 5 | console.log('Contact Ctrl' + $scope); 6 | }]); 7 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/history.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('HistoryCtrl', [ '$scope', function HistoryCtrl($scope) { 5 | console.log('History Ctrl' + $scope); 6 | }]); 7 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/notification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('NotificationCtrl', [ '$scope', function NotificationCtrl($scope) { 5 | console.log('Notification Ctrl' + $scope); 6 | }]); 7 | -------------------------------------------------------------------------------- /sbirez/templates/emails/submit_notification-body-text.html: -------------------------------------------------------------------------------- 1 | Your proposal, {{ proposal.title }}, has been submitted to the appropriate 2 | program office. You will be notified of selection status within 180 days. 3 | 4 | Thank you, 5 | 6 | SBIR-EZ Team 7 | -------------------------------------------------------------------------------- /tests/client/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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", "afsbirez.settings.dev") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/footer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('footer', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | templateUrl: 'static/views/partials/footer.html', 8 | controller: [function () { 9 | 10 | }] 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /data/empty_topic_data.sql: -------------------------------------------------------------------------------- 1 | DROP SCHEMA import CASCADE; 2 | DELETE FROM public.sbirez_keyword_topics; 3 | DELETE FROM public.sbirez_area_topics; 4 | DELETE FROM public.sbirez_reference; 5 | DELETE FROM public.sbirez_phase; 6 | DELETE FROM public.sbirez_topic; 7 | DELETE FROM public.sbirez_area; 8 | DELETE FROM public.sbirez_keyword; 9 | -------------------------------------------------------------------------------- /sbirez/templatetags/stringyesno.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.template.defaultfilters import stringfilter 3 | 4 | register = template.Library() 5 | 6 | @register.filter 7 | @stringfilter 8 | def stringyesno(value): 9 | if value == "true": 10 | return "yes" 11 | else: 12 | return "no" 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sbirez", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "bower": "^1.3.8" 6 | }, 7 | "devDependencies": { 8 | "karma": "~0.10.9", 9 | "karma-firefox-launcher": "~0.1.3", 10 | "karma-jasmine": "~0.1.5", 11 | "karma-ng-html2js-preprocessor": "*", 12 | "should": "~2.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/calculated.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /sbirez/migrations/0027_merge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0026_auto_20150720_2156'), 11 | ('sbirez', '0026_merge'), 12 | ] 13 | 14 | operations = [ 15 | ] 16 | -------------------------------------------------------------------------------- /sbirez/migrations/0034_merge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0033_auto_20150910_2059'), 11 | ('sbirez', '0033_merge'), 12 | ] 13 | 14 | operations = [ 15 | ] 16 | -------------------------------------------------------------------------------- /sbirez/migrations/0023_merge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0022_auto_20150420_2019'), 11 | ('sbirez', '0021_auto_20150420_2048'), 12 | ] 13 | 14 | operations = [ 15 | ] 16 | -------------------------------------------------------------------------------- /sbirez/migrations/0026_merge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0024_auto_20150609_1933'), 11 | ('sbirez', '0025_auto_20150706_2356'), 12 | ] 13 | 14 | operations = [ 15 | ] 16 | -------------------------------------------------------------------------------- /sbirez/migrations/0031_merge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0030_auto_20150817_1927'), 11 | ('sbirez', '0030_element_report_text'), 12 | ] 13 | 14 | operations = [ 15 | ] 16 | -------------------------------------------------------------------------------- /chef/Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://api.berkshelf.com' 2 | 3 | cookbook 'user', git: 'https://github.com/fnichol/chef-user' 4 | cookbook 'afsbirez', path: '/Users/davidbest/Development/18F/afsbirez-cookbook' 5 | cookbook 'postgresql', '3.4.4' 6 | cookbook 'nginx' 7 | cookbook 'nodejs' 8 | cookbook 'apt' 9 | cookbook 'build-essential' 10 | cookbook 'citadel', git: 'https://github.com/dlapiduz/citadel.git' 11 | -------------------------------------------------------------------------------- /sbirez/migrations/0033_merge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0032_element_report_question_number'), 11 | ('sbirez', '0031_auto_20150828_1751'), 12 | ] 13 | 14 | operations = [ 15 | ] 16 | -------------------------------------------------------------------------------- /data/refresh.sh: -------------------------------------------------------------------------------- 1 | 2 | if [ ! -f aftopics.json ]; then 3 | echo "Topic scrape results needed at aftopics.json" 4 | exit -1 5 | fi 6 | #dropdb sbirezdev; createdb sbirezdev 7 | #python ../manage.py db upgrade 8 | python make_unique.py # produces topics.json, with no duplicate entries, from aftopics.json 9 | ddlgenerator --inserts postgresql topics.json > topics.sql 10 | psql -f import_topics.sql afsbirez 11 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/backbutton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('backButton', function(){ 4 | return { 5 | restrict: 'A', 6 | 7 | link: function(scope, element) { 8 | function goBack() { 9 | history.back(); 10 | scope.$apply(); 11 | } 12 | 13 | element.bind('click', goBack); 14 | } 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /sbirez/fixtures/solicitation.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fields": { 4 | "element": 1, 5 | "name": "DoD SBIR 2015.1", 6 | "pre_release_date": "2014-12-12T05:00:00Z", 7 | "proposals_begin_date": "2015-01-15T05:00:00Z", 8 | "proposals_end_date": "2015-02-18T05:00:00Z" 9 | }, 10 | "model": "sbirez.solicitation", 11 | "pk": 1 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /sbirez/static/images/svg/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /sbirez/migrations/0018_merge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0015_auto_20150326_1735'), 11 | ('sbirez', '0017_auto_20150403_1944'), 12 | ('sbirez', '0017_auto_20150330_2139'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /sbirez/static/images/svg/checkmark_green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /generate_fixtures.sh: -------------------------------------------------------------------------------- 1 | # Generating a dumpfile without this leads to errors like 2 | # django.db.utils.IntegrityError: Problem installing fixture '/home/catherine/werk/afsbirez/sbirez/fixtures/topictest.json': Could not load contenttypes.ContentType(pk=12): duplicate key value violates unique constraint "django_content_type_app_label_611081bc506e4133_uniq" 3 | python manage.py dumpdata --exclude=contenttypes --exclude=auth.Permission > sbirez/fixtures/topictest.json 4 | -------------------------------------------------------------------------------- /data/make_unique.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def filter_json_for_unique(infile_name, outfile_name): 4 | with open(infile_name) as infile: 5 | content = json.load(infile) 6 | 7 | uniques = set(str(c) for c in content) 8 | uniques = [eval(u) for u in uniques] 9 | 10 | with open(outfile_name, 'w') as outfile: 11 | json.dump(uniques, outfile) 12 | 13 | 14 | if __name__ == '__main__': 15 | filter_json_for_unique('aftopics.json', 'topics.json') -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/group.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('group', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | group: '=', 9 | method: '=' 10 | }, 11 | templateUrl: 'static/views/partials/elements/group.html', 12 | controller: ['$scope', 13 | function ($scope) { 14 | $scope.element = $scope.group; 15 | } 16 | ] 17 | }; 18 | }); 19 | -------------------------------------------------------------------------------- /sbirez/templates/emails/mock_submission-body-text.html: -------------------------------------------------------------------------------- 1 | Direct upstream submission of your proposal is not yet 2 | supported. Here are your proposal details: 3 | 4 | Title: {{proposal.title}} 5 | owner: {{proposal.owner.name}} 6 | firm: {{proposal.firm.name}} 7 | workflow: {{proposal.workflow.name}} 8 | topic: {{proposal.topic.topic_number}} {{proposal.topic.title}} 9 | submitted at: {{proposal.submitted_at}} 10 | proposal details: 11 | 12 | {{proposal.data|safe}} 13 | 14 | - SBIR-EZ Team 15 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/adminuser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('AdminUserCtrl', ['$scope', 5 | function AdminUserCtrl($scope) { 6 | 7 | $scope.introMessage = function(username, password) { 8 | if (username === 'doduser' && password === 'sbireztest') { 9 | $scope.closeThisDialog(); 10 | } 11 | else { 12 | $scope.errorMsg = 'Invalid credentials.'; 13 | } 14 | }; 15 | } 16 | ]); 17 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": false, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "angular": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sbirez/templatetags/money.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.template.defaultfilters import stringfilter 3 | 4 | from django.contrib.humanize.templatetags.humanize import intcomma 5 | 6 | register = template.Library() 7 | 8 | @register.filter 9 | @stringfilter 10 | def money(dollars): 11 | print(dollars) 12 | try: 13 | dollars = round(float(dollars), 2) 14 | return "%s%s" % (intcomma(int(dollars)), ("%0.2f" % dollars)[-3:]) 15 | except ValueError: 16 | return "" 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/text.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | {{validationstorage}} 7 |
8 | -------------------------------------------------------------------------------- /chef/roles/vagrant.rb: -------------------------------------------------------------------------------- 1 | name "vagrant" 2 | description "Vagrant with sbirez and its dependencies" 3 | 4 | run_list "recipe[apt]", "recipe[build-essential::default]", "nginx", "afsbirez::db", "afsbirez" 5 | 6 | default_attributes( 7 | postgresql: { version: '9.3' }, 8 | nodejs: {npm: '1.4.23' } 9 | ) 10 | 11 | override_attributes( 12 | sbirez: { 13 | nginx_conf_dir: "/etc/nginx/sites-enabled", 14 | nginx_default: "default", 15 | nginx_conf_source: "tools/nginx/sites-enabled.default" 16 | } 17 | ) 18 | 19 | 20 | -------------------------------------------------------------------------------- /sbirez/migrations/0021_auto_20150420_2018.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0020_auto_20150414_1920'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='documentversion', 16 | name='file', 17 | field=models.FileField(upload_to=''), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /sbirez/migrations/0002_auto_20150219_1951.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='phase', 16 | name='phase', 17 | field=models.TextField(), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /sbirez/migrations/0029_auto_20150804_2055.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0028_auto_20150724_2049'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='proposal', 16 | name='created_at', 17 | field=models.DateTimeField(auto_now_add=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /sbirez/migrations/0030_element_report_text.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0029_auto_20150804_2055'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='element', 16 | name='report_text', 17 | field=models.TextField(blank=True, null=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /sbirez/migrations/0032_element_report_question_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0031_merge'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='element', 16 | name='report_question_number', 17 | field=models.TextField(blank=True, null=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /sbirez/migrations/0026_auto_20150720_2156.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_pgjson.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0025_auto_20150706_2356'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='element', 17 | name='required', 18 | field=models.TextField(default='False'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.min.css 2 | *.min.js 3 | *.min.*.js 4 | *.min.*.css 5 | .webassets-cache/ 6 | .DS_Store 7 | *.pyc 8 | *.swp 9 | main.css 10 | node_modules 11 | public 12 | .tmp 13 | .sass-cache 14 | heroku 15 | /views 16 | dist 17 | data_store 18 | uploads 19 | env 20 | venv 21 | .idea 22 | bower_components 23 | lib 24 | .vagrant/ 25 | src/ 26 | data/*.json 27 | *.log 28 | Untitled*.ipynb 29 | .floo* 30 | *.wpr 31 | *.wpu 32 | .ipynb_checkpoints/ 33 | *.egg-info/ 34 | sbirez/media/* 35 | afsbirez/settings/credentials.py 36 | cf-ssh.yml 37 | *.mdb 38 | *deathstarplans.txt 39 | -------------------------------------------------------------------------------- /sbirez/migrations/0003_auto_20150219_1959.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0002_auto_20150219_1951'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='topic', 16 | name='fts', 17 | field=models.TextField(blank=True, null=True), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /afsbirez/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for afsbirez 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.7/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "afsbirez.settings.production") 12 | 13 | from whitenoise.django import DjangoWhiteNoise 14 | 15 | from django.core.wsgi import get_wsgi_application 16 | application = get_wsgi_application() 17 | application = DjangoWhiteNoise(application) 18 | -------------------------------------------------------------------------------- /sbirez/static/images/icons.fallback.css: -------------------------------------------------------------------------------- 1 | .icon-logo { 2 | background-image: url('png/logo.png'); 3 | background-repeat: no-repeat; 4 | } 5 | 6 | .icon-search, 7 | header.banner form div.field::after { 8 | background-image: url('png/search.png'); 9 | background-repeat: no-repeat; 10 | } 11 | 12 | .icon-checkmark-green, 13 | .is-complete::after { 14 | background-image: url('png/checkmark_green.png'); 15 | background-repeat: no-repeat; 16 | } 17 | 18 | .icon-cross-red, 19 | .is-error::after { 20 | background-image: url('png/cross_red.png'); 21 | background-repeat: no-repeat; 22 | } -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/checkbox.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | {{validationstorage}} 6 |
7 | -------------------------------------------------------------------------------- /.cfignore: -------------------------------------------------------------------------------- 1 | sbirez/media/ 2 | .bash* 3 | .bowerrc 4 | .jshintrc 5 | .travis.yml 6 | *.md 7 | __pycache__ 8 | aftopics.json 9 | apiary.apib 10 | karma*.js 11 | env 12 | venv 13 | sbirez/static/.webassets-cache/ 14 | tests 15 | chef 16 | .git 17 | package.json 18 | bower.json 19 | .DS_Store 20 | *.pyc 21 | *.swp 22 | node_modules 23 | public 24 | .tmp 25 | .sass-cache 26 | heroku 27 | .heroku 28 | /views 29 | data_store 30 | uploads 31 | env 32 | .idea 33 | bower_components 34 | .vagrant/ 35 | *.log 36 | Untitled*.ipynb 37 | .floo* 38 | *.wpr 39 | *.wpu 40 | .ipynb_checkpoints/ 41 | *.egg-info/ 42 | -------------------------------------------------------------------------------- /sbirez/migrations/0019_auto_20150406_0018.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0018_merge'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='topic', 16 | name='solicitation', 17 | field=models.ForeignKey(default=1, related_name='solicitation', to='sbirez.Solicitation'), 18 | preserve_default=False, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/documentList.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('DocumentListCtrl', function ($scope, $rootScope, $http, $upload, $window, DocumentService) { 5 | $scope.docList = []; 6 | 7 | $scope.sortType = 'name'; 8 | $scope.sortReverse = false; 9 | 10 | DocumentService.list().then(function(data) { 11 | $scope.docList = data.results; 12 | }); 13 | $rootScope.$on('fileAdded', function() { 14 | DocumentService.list().then(function(data) { 15 | $scope.docList = data.results; 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /sbirez/migrations/0016_auto_20150331_1737.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0015_auto_20150327_1426'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='proposal', 17 | name='workflow', 18 | field=models.ForeignKey(related_name='proposals', to='sbirez.Element'), 19 | preserve_default=True, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /sbirez/migrations/0033_auto_20150910_2059.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0032_auto_20150910_0836'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='pointofcontactrelationship', 16 | options={'ordering': ['priority']}, 17 | ), 18 | migrations.RenameField( 19 | model_name='pointofcontactrelationship', 20 | old_name='order', 21 | new_name='priority', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /sbirez/static/js/services/authsvc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').factory('AuthenticationService', function() { 4 | var observerCallbacks = []; 5 | return { 6 | registerObserverCallback : function(callback) { 7 | observerCallbacks.push(callback); 8 | }, 9 | 10 | getAuthenticated : function() { 11 | return this.isAuthenticated; 12 | }, 13 | 14 | setAuthenticated : function(value) { 15 | this.isAuthenticated = value; 16 | angular.forEach(observerCallbacks, function(callback) { 17 | if (callback) { 18 | callback(); 19 | } 20 | }); 21 | }, 22 | 23 | isAuthenticated: false 24 | }; 25 | }); 26 | -------------------------------------------------------------------------------- /sbirez/static/images/svg/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /afsbirez/settings/dev.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .base import * 4 | from django.utils.crypto import get_random_string 5 | 6 | DEBUG = True 7 | TEMPLATE_DEBUG = True 8 | 9 | INTERNAL_IPS = ('127.0.0.1',) 10 | SECRET_KEY = get_random_string(50) 11 | 12 | DATABASES = { 13 | "default": { 14 | "ENGINE": "django.db.backends.postgresql_psycopg2", 15 | "NAME": 'afsbirez', 16 | "USER": 'afsbirez', 17 | "PASSWORD": 'afsbirez', 18 | "HOST": '127.0.0.1', 19 | }, 20 | } 21 | 22 | DJMAIL_REAL_BACKEND="django.core.mail.backends.console.EmailBackend" 23 | INSTALLED_APPS.append('django_extensions') 24 | 25 | REST_PROXY['API_KEY'] = 'DEMO_KEY' 26 | 27 | SSLIFY_DISABLE = True 28 | -------------------------------------------------------------------------------- /sbirez/migrations/0027_auto_20150724_2048.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_pgjson.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0027_merge'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='proposal', 17 | old_name='submitted_at', 18 | new_name='created_at', 19 | ), 20 | migrations.AlterField( 21 | model_name='proposal', 22 | name='data', 23 | field=django_pgjson.fields.JsonField(blank=True, null=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /sbirez/migrations/0028_auto_20150724_2049.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0027_auto_20150724_2048'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='proposal', 16 | name='submitted_at', 17 | field=models.DateTimeField(null=True, blank=True), 18 | ), 19 | migrations.AddField( 20 | model_name='proposal', 21 | name='verified_at', 22 | field=models.DateTimeField(null=True, blank=True), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /sbirez/management/commands/modules/splitting.py: -------------------------------------------------------------------------------- 1 | import doctest 2 | import re 3 | from itertools import zip_longest 4 | 5 | def grouper(iterable, n, fillvalue=None): 6 | "Collect data into fixed-length chunks or blocks" 7 | # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx 8 | args = [iter(iterable)] * n 9 | return zip_longest(fillvalue=fillvalue, *args) 10 | 11 | def split_retaining_splitter(splitter, target): 12 | """ 13 | >>> splitter = re.compile(r'([\w]+:)') 14 | >>> split_retaining_splitter(splitter, 'a: b c: d e:f') 15 | ['a: b', 'c: d', 'e:f'] 16 | """ 17 | tuples = grouper(splitter.split(target)[1:], 2) 18 | return [''.join(t).strip() for t in tuples] 19 | 20 | if __name__ == '__main__': 21 | doctest.testmod() 22 | -------------------------------------------------------------------------------- /sbirez/migrations/0022_auto_20150420_2019.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0021_auto_20150420_2018'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='question', 16 | name='parent', 17 | ), 18 | migrations.RemoveField( 19 | model_name='question', 20 | name='subworkflow', 21 | ), 22 | migrations.DeleteModel( 23 | name='Question', 24 | ), 25 | migrations.DeleteModel( 26 | name='Workflow', 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/reset.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Reset your password

5 |

We will send a password reset link to the email address below:

6 |
7 | 11 | {{errorMsg}} 12 |
13 | 17 |
18 |
19 |
-------------------------------------------------------------------------------- /sbirez/static/views/partials/landing.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Tell us about your company now?

4 |

Before you can submit a proposal on a solicitation, you’ll need to tell us a little bit about your company. You can do that at any time by clicking My Company in the header.

5 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | memory: 512M 2 | instances: 1 3 | buildpack: https://github.com/ddollar/heroku-buildpack-multi.git 4 | applications: 5 | - name: sbirez 6 | timeout: 180 7 | command: export PATH=$PATH:/app/.heroku/src/pywkher/bin && waitress-serve --port=$VCAP_APP_PORT afsbirez.wsgi:application 8 | #command: python manage.py migrate --noinput && waitress-serve --port=$VCAP_APP_PORT afsbirez.wsgi:application 9 | #command: export PATH=$PATH:/app/.heroku/src/pywkher/bin && python manage.py migrate && python manage.py readworkflow data/dod_workflow.yaml && python manage.py loaddata sbirez/fixtures/solicitation.json && python manage.py importtopics data/finaltopics.mdb "DoD SBIR 2015.1" && python manage.py getnaics && python manage.py indextopics && waitress-serve --port=$VCAP_APP_PORT afsbirez.wsgi:application 10 | -------------------------------------------------------------------------------- /tests/client/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "jasmine": false, 33 | "spyOn": false 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /sbirez/migrations/0012_auto_20150317_1646.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0011_auto_20150317_1630'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='question', 17 | name='validation_msg', 18 | field=models.TextField(blank=True), 19 | preserve_default=True, 20 | ), 21 | migrations.AddField( 22 | model_name='question', 23 | name='default', 24 | field=models.TextField(blank=True), 25 | preserve_default=True, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /sbirez/migrations/0021_auto_20150420_2048.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_pgjson.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0020_auto_20150414_1920'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='documentversion', 17 | name='file', 18 | field=models.FileField(upload_to=''), 19 | ), 20 | migrations.RunSQL(sql=""" 21 | ALTER TABLE sbirez_proposal 22 | ALTER COLUMN data TYPE JSON USING (data::JSON)""", 23 | reverse_sql="""ALTER TABLE sbirez_proposal 24 | ALTER COLUMN data TYPE text"""), 25 | ] 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cssmin==0.2.0 2 | dateutils==0.6.6 3 | ddlgenerator==0.1.9 4 | Django==1.8 5 | django-assets==0.10 6 | django-custom-user==0.5 7 | django-downloadview==1.6 8 | django-pgjson==0.2.3 9 | django-rest-auth==0.3.4 10 | django-rest-framework-proxy==1.4.0 11 | django-sslify==0.2.7 12 | djangorestframework==3.1.1 13 | djangorestframework-hstore==1.2 14 | djangorestframework-jwt==1.4.0 15 | djangorestframework-recursive==0.1.0 16 | dj-database-url==0.3.0 17 | djmail==0.10.0 18 | djorm-ext-pgfulltext==0.9.3 19 | jsmin==2.1.1 20 | marshmallow==1.2.2 21 | pdfkit==0.5.0 22 | -e git+git://github.com/hannal/PyPDF2.git#egg=PyPDF2 23 | psycopg2==2.6 24 | pyscss==1.3.4 25 | python-levenshtein==0.12.0 26 | -e git+git://github.com/leonardoo/pywkher.git#egg=pywkher 27 | waitress==0.8.9 28 | whitenoise==1.0.6 29 | xlrd==0.9.3 30 | -r requirements/dev.txt 31 | -------------------------------------------------------------------------------- /sbirez/migrations/0024_auto_20150612_1611.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_pgjson.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0023_merge'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Jargon', 17 | fields=[ 18 | ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), 19 | ('name', models.TextField(unique=True)), 20 | ('html', models.TextField()), 21 | ('elements', models.ManyToManyField(to='sbirez.Element', related_name='jargons', null=True, blank=True)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /sbirez/fixtures/naics.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fields": { 4 | "description": "Crop Production", 5 | "firms": [] 6 | }, 7 | "model": "sbirez.naics", 8 | "pk": "111" 9 | }, 10 | { 11 | "fields": { 12 | "description": "Oilseed and Grain Farming", 13 | "firms": [] 14 | }, 15 | "model": "sbirez.naics", 16 | "pk": "1111" 17 | }, 18 | { 19 | "fields": { 20 | "description": "Soybean Farming", 21 | "firms": [] 22 | }, 23 | "model": "sbirez.naics", 24 | "pk": "11111" 25 | }, 26 | { 27 | "fields": { 28 | "description": "Soybean Farming", 29 | "firms": [] 30 | }, 31 | "model": "sbirez.naics", 32 | "pk": "111110" 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /sbirez/templates/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | {% include "fragments/password_reset_confirm_form.html" %} 7 |
8 |
9 | {% endblock %} 10 | 11 | {% block script %} 12 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sbirez", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "1.3.14", 6 | "json3": "~3.3.2", 7 | "es5-shim": "~4.0.6", 8 | "angular-resource": "~1.3", 9 | "angular-cookies": "~1.3", 10 | "angular-sanitize": "~1.3", 11 | "angular-route": "~1.3", 12 | "ng-file-upload": "~3.2.4", 13 | "ngDialog": "~0.3.9", 14 | "angular-aria": "~1.3", 15 | "angular-ui-router": "~0.2.13", 16 | "bootstrap-sass": "~3.3.3", 17 | "angular-order-object-by": "~1.1.0", 18 | "jquery": "~2.1.4", 19 | "jquery-ui": "~1.11.4", 20 | "base64": "0.3.0", 21 | "bigfoot": "2.1.3" 22 | }, 23 | "devDependencies": { 24 | "angular-mocks": "~1.3", 25 | "angular-scenario": "~1.3" 26 | }, 27 | "testPath": "test/client/spec", 28 | "resolutions": { 29 | "angular": "1.3.20" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/str.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 | 10 | 11 | {{validationstorage}} 12 |
13 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/main.html: -------------------------------------------------------------------------------- 1 |
2 |

Let’s work together

3 |

The Small Business Innovation Research (SBIR) and Small Business Technology Transfer (STTR) programs encourage domestic small businesses to engage in Federal Research/Research and Development with the potential for commercialization. This site contains the list of Air Force topics.

4 |

Use the search form below to find projects that dovetail with the work that you do.

5 |
6 |
7 | 11 |
12 | 15 |
16 |
17 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/savedOpps.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('SavedOppsCtrl', function ($scope, $rootScope, $state, SavedOpportunityService, SearchService) { 5 | $scope.data = {}; 6 | $scope.queryData = ''; 7 | $rootScope.bodyClass = 'proposals'; 8 | 9 | SavedOpportunityService.list().then(function(data){ 10 | $scope.data = data; 11 | if ($scope.data.results && $scope.data.results.length === 0) { 12 | $rootScope.bodyClass = 'proposals proposals-no-results'; 13 | } else { 14 | $rootScope.bodyClass = 'proposals'; 15 | } 16 | }); 17 | 18 | $scope.search = function() { 19 | SearchService.search(1, $scope.queryData, 10).then(function(data) { 20 | $state.go('app.search', {}, {'reload':true}); 21 | }, function(error) { 22 | console.log(error); 23 | }); 24 | }; 25 | }); 26 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/reset.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('ResetCtrl', function ($scope, $rootScope, $window, $state, UserService) { 5 | $rootScope.bodyClass = 'sign-up'; 6 | $scope.email = ''; 7 | $scope.errorMsg = ''; 8 | 9 | $scope.reset = function reset() { 10 | $scope.errorMsg = ''; 11 | if ($scope.email !== '') { 12 | UserService.resetPassword($scope.email).then(function(data) { 13 | $scope.successMsg = data.data.success; 14 | }, function(status) { 15 | if (status && status.data && status.data.non_field_errors) { 16 | $scope.errorMsg = status.data.non_field_errors[0]; 17 | } else { 18 | $scope.errorMsg = 'Unable to reset password.'; 19 | } 20 | }); 21 | } else { 22 | $scope.errorMsg = 'Please provide an email address.'; 23 | } 24 | }; 25 | }); 26 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/form.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('FormCtrl', function ($scope, $http) { 5 | $scope.formData = {}; 6 | $scope.formId = {}; 7 | $scope.formSchema = null; 8 | $scope.formFields = ['*', {'type':'submit', 'title': 'Submit'}]; 9 | 10 | $http.get('/api/forms').success(function(list) { 11 | $scope.formList = list; 12 | }); 13 | 14 | $scope.setForm = function(id) { 15 | $http.get('/api/forms/' + id).success(function(fields) { 16 | $scope.formSchema = fields; 17 | $scope.formId = id; 18 | }); 19 | }; 20 | 21 | $scope.formOptions = { 22 | uniqueFormId: 'myFormId', 23 | hideSubmit: false 24 | }; 25 | 26 | $scope.onSubmit = function() { 27 | console.log('form submitted:', $scope.formData); 28 | $http.post('/api/forms/' + $scope.formId, $scope.formData).success(console.log('saved')); 29 | }; 30 | }); 31 | -------------------------------------------------------------------------------- /tests/client/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | /* 5 | No tests needed for main right now. 6 | // load the controller's module 7 | beforeEach(module('sbirezApp')); 8 | 9 | var MainCtrl, 10 | scope, 11 | $httpBackend; 12 | 13 | // Initialize the controller and a mock scope 14 | beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) { 15 | $httpBackend = _$httpBackend_; 16 | $httpBackend.expectGET('/api/awesomeThings') 17 | .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']); 18 | scope = $rootScope.$new(); 19 | MainCtrl = $controller('MainCtrl', { 20 | $scope: scope 21 | }); 22 | })); 23 | 24 | it('should attach a list of awesomeThings to the scope', function () { 25 | expect(scope.awesomeThings).toBeUndefined(); 26 | $httpBackend.flush(); 27 | expect(scope.awesomeThings.length).toBe(4); 28 | }); 29 | */ 30 | }); 31 | -------------------------------------------------------------------------------- /data/fulltext.sql: -------------------------------------------------------------------------------- 1 | SET search_path=public; 2 | 3 | WITH subq AS ( 4 | SELECT t.id, 5 | setweight(to_tsvector(string_agg(coalesce(t.title, ''), ' ')), 'A') || 6 | setweight(to_tsvector(string_agg(coalesce(t.description, ''), ' ')), 'B') || 7 | setweight(to_tsvector(string_agg(coalesce(a.area, ''), ' ')), 'A') || 8 | setweight(to_tsvector(string_agg(coalesce(k.keyword, ''), ' ')), 'A') 9 | AS weights 10 | FROM sbirez_topic t 11 | JOIN sbirez_area_topics ta ON (t.id = ta.topic_id) 12 | JOIN sbirez_area a ON (ta.area_id = a.id) 13 | JOIN sbirez_keyword_topics tk ON (t.id = tk.topic_id) 14 | JOIN sbirez_keyword k ON (tk.keyword_id = k.id) 15 | GROUP BY t.id) 16 | UPDATE sbirez_topic 17 | SET fts = (SELECT weights FROM subq 18 | WHERE sbirez_topic.id = subq.id); 19 | 20 | -- appears to be created by sqlalchemy-searchable already 21 | -- CREATE INDEX topics_fulltext_idx ON topics USING gin(full_text); 22 | -------------------------------------------------------------------------------- /sbirez/migrations/0017_auto_20150403_1944.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0016_auto_20150331_1737'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='sbirezuser', 17 | name='groups', 18 | field=models.ManyToManyField(verbose_name='groups', related_name='user_set', related_query_name='user', blank=True, to='auth.Group', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.'), 19 | ), 20 | migrations.AlterField( 21 | model_name='sbirezuser', 22 | name='last_login', 23 | field=models.DateTimeField(blank=True, null=True, verbose_name='last login'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/header.html: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/postsignup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Sign in

5 |
6 | 11 |
12 |
13 | 18 |
19 |

{{errorMsg}}

20 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /sbirez/migrations/0004_auto_20150223_2159.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0003_auto_20150219_1959'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RunSQL("""ALTER TABLE sbirez_topic ALTER COLUMN fts TYPE tsvector USING (fts::tsvector)""", 16 | reverse_sql="""ALTER TABLE sbirez_topic ALTER COLUMN fts TYPE text USING (fts::text)""") 17 | ] 18 | 19 | # we don't use the auto-generated migrations.AlterField, because that throws an error: 20 | # django.db.utils.ProgrammingError: column "fts" cannot be cast automatically to type tsvector 21 | # HINT: Specify a USING expression to perform the conversion. 22 | # sql was 23 | # ALTER TABLE "sbirez_topic" ALTER COLUMN "fts" TYPE tsvector, ALTER COLUMN "fts" SET DEFAULT %s 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sbirez/migrations/0007_auto_20150303_1801.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | from django.conf import settings 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('sbirez', '0006_auto_20150302_2037'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='SavedTopic', 19 | fields=[ 20 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), 21 | ('topic', models.ForeignKey(related_name='savedtopics', to='sbirez.Topic')), 22 | ('user', models.ForeignKey(related_name='savedtopics', to=settings.AUTH_USER_MODEL)), 23 | ], 24 | options={ 25 | }, 26 | bases=(models.Model,), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/savedOpps.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

My Proposals

4 |
5 |

You’ve not yet started a proposal.

6 |

To get started, search for a topic (like "aircraft", "software", or "electrochemical") and click "Start a proposal" on a result that interests you.

7 |
8 |
9 | 13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/lineitem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('lineitem', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | lineitem: '=', 9 | proposal: '@' 10 | }, 11 | templateUrl: 'static/views/partials/elements/lineitem.html', 12 | controller: ['$scope', 'ProposalService', 13 | function ($scope, ProposalService) { 14 | $scope.element = $scope.lineitem; 15 | $scope.visible = true; 16 | $scope.visibleCount = $scope.element.multiplicity.length; 17 | 18 | var askIfCallback = function(data) { 19 | $scope.visible = (data === true || data === 'true'); 20 | }; 21 | $scope.storage = ProposalService.register($scope.element, 22 | null, 23 | $scope.element.ask_if !== null ? askIfCallback : null, 24 | $scope.multipletoken); 25 | } 26 | ] 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/readonlytext.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('readonlytext', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | readonlytext: '=', 9 | multiplename: '=?' 10 | }, 11 | templateUrl: 'static/views/partials/elements/readonlytext.html', 12 | controller: ['$scope', 13 | function ($scope) { 14 | $scope.element = $scope.readonlytext; 15 | $scope.fieldName = $scope.element.human; 16 | 17 | $scope.fieldName = $scope.element.human; 18 | if ($scope.multiplename !== undefined && $scope.element.human.indexOf('%multiple%') > -1) { 19 | $scope.fieldName = $scope.element.human.replace('%multiple%', $scope.multiplename); 20 | } 21 | 22 | if ($scope.multiplename !== undefined && $scope.fieldName.indexOf('%multiple%') > -1) { 23 | $scope.fieldName = $scope.fieldName.replace('%multiple%', $scope.multiplename); 24 | } 25 | } 26 | ] 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/singlelineitem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('singlelineitem', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | singlelineitem: '=', 9 | proposal: '@' 10 | }, 11 | templateUrl: 'static/views/partials/elements/singlelineitem.html', 12 | controller: ['$scope', 'ProposalService', 13 | function ($scope, ProposalService) { 14 | $scope.element = $scope.singlelineitem; 15 | $scope.visible = true; 16 | $scope.visibleCount = $scope.element.multiplicity.length; 17 | 18 | var askIfCallback = function(data) { 19 | $scope.visible = (data === true || data === 'true'); 20 | }; 21 | $scope.storage = ProposalService.register($scope.element, 22 | null, 23 | $scope.element.ask_if !== null ? askIfCallback : null, 24 | $scope.multipletoken); 25 | } 26 | ] 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /sbirez/templates/fragments/password_reset_confirm_form.html: -------------------------------------------------------------------------------- 1 |
2 |

Password reset

3 | 4 | 5 | 6 |
7 | 11 |
12 | 13 |
14 | 18 |
19 | 20 |
21 | 22 |
23 | 24 | 27 |
28 | -------------------------------------------------------------------------------- /sbirez/static/js/filters/truncate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Truncate Filter 4 | * @Param string 5 | * @Param int, default = 10 6 | * @Param string, default = "…" 7 | * @return string 8 | */ 9 | angular.module('sbirezApp') 10 | .filter('truncate', function () { 11 | return function (text, length, end) { 12 | if (isNaN(length)) { 13 | length = 10; 14 | } 15 | 16 | if (end === undefined) { 17 | end = '…'; 18 | } 19 | 20 | if (text && (text.length <= length || text.length - end.length <= length)) { 21 | return text; 22 | } 23 | else if (text) { 24 | return String(text).substring(0, length-end.length) + end; 25 | } 26 | }; 27 | }); 28 | 29 | 30 | /** 31 | * Example - see the jsfiddle: http://jsfiddle.net/tUyyx/ 32 | * 33 | * var myText = "This is an example."; 34 | * 35 | * {{myText|truncate}} 36 | * {{myText|truncate:5}} 37 | * {{myText|truncate:25:" ->"}} 38 | * 39 | * Output 40 | * "This is..." 41 | * "Th..." 42 | * "This is an e ->" 43 | * 44 | */ 45 | -------------------------------------------------------------------------------- /tests/client/spec/controllers/accountUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AccountUserCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('sbirezApp')); 7 | 8 | var AccountUserCtrl, 9 | $rootScope, 10 | scope, 11 | UserService, 12 | $q; 13 | 14 | // Initialize the controller and a mock scope 15 | beforeEach(inject(function ($controller, _$rootScope_, _UserService_, _$q_) { 16 | UserService = _UserService_; 17 | $rootScope = _$rootScope_; 18 | $q = _$q_; 19 | scope = $rootScope.$new(); 20 | spyOn(UserService, 'changePassword'); 21 | AccountUserCtrl = $controller('AccountUserCtrl', { 22 | $scope: scope 23 | }); 24 | })); 25 | 26 | xit('should call changePassword when savePassword is called if form is properly filled out', function() { 27 | scope.input.oldpassword = 'abc'; 28 | scope.input.newpassword1 = '123'; 29 | scope.input.newpassword2 = '123'; 30 | scope.savePassword(); 31 | expect(UserService.changePassword).toHaveBeenCalled(); 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/bool.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 8 |
9 |
10 | 14 |
15 |
{{validationstorage}}
16 |
17 | -------------------------------------------------------------------------------- /sbirez/migrations/0008_auto_20150304_2134.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | import djorm_pgfulltext.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('sbirez', '0007_auto_20150303_1801'), 14 | ] 15 | 16 | operations = [ 17 | migrations.RemoveField( 18 | model_name='savedtopic', 19 | name='topic', 20 | ), 21 | migrations.RemoveField( 22 | model_name='savedtopic', 23 | name='user', 24 | ), 25 | migrations.DeleteModel( 26 | name='SavedTopic', 27 | ), 28 | migrations.AddField( 29 | model_name='topic', 30 | name='saved_by', 31 | field=models.ManyToManyField(related_name='saved_topics', null=True, to=settings.AUTH_USER_MODEL, blank=True), 32 | preserve_default=True, 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/upload.html: -------------------------------------------------------------------------------- 1 |
2 | {{element.human}} 3 |
4 | 16 | {{validationstorage}} 17 |
18 |
19 | -------------------------------------------------------------------------------- /sbirez/static/images/svg/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /sbirez/static/images/svg/cross_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /sbirez/static/css/ngdialog-theme-intromessage.css: -------------------------------------------------------------------------------- 1 | .ngdialog.ngdialog-theme-intromessage { 2 | padding-bottom: 160px; 3 | padding-top: 160px; 4 | } 5 | 6 | .ngdialog.ngdialog-theme-intromessage .ngdialog-content { 7 | background: #f0f0f0; 8 | border-radius: 5px; 9 | border: 2px solid #337AB7; 10 | color: #444; 11 | font-size: 1.1em; 12 | line-height: 1.5em; 13 | margin: 0 auto; 14 | max-width: 100%; 15 | padding: 1em; 16 | position: relative; 17 | width: 90%; 18 | } 19 | 20 | .ngdialog.ngdialog-theme-intromessage .ngdialog-close { 21 | border-radius: 5px; 22 | cursor: pointer; 23 | position: absolute; 24 | right: 0; 25 | top: 0; 26 | } 27 | 28 | .ngdialog.ngdialog-theme-intromessage .ngdialog-close:before { 29 | background: transparent; 30 | border-radius: 3px; 31 | color: #bbb; 32 | content: '\00D7'; 33 | font-size: 26px; 34 | font-weight: 400; 35 | height: 30px; 36 | line-height: 26px; 37 | position: absolute; 38 | right: 3px; 39 | text-align: center; 40 | top: 3px; 41 | width: 30px; 42 | } 43 | 44 | .ngdialog.ngdialog-theme-intromessage .ngdialog-close:hover:before, 45 | .ngdialog.ngdialog-theme-intromessage .ngdialog-close:active:before { 46 | color: #777; 47 | } 48 | -------------------------------------------------------------------------------- /sbirez/migrations/0013_auto_20150317_1907.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | from django.conf import settings 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('sbirez', '0012_auto_20150317_1646'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Proposal', 18 | fields=[ 19 | ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), 20 | ('submitted_at', models.DateTimeField(auto_now=True)), 21 | ('data', models.TextField(null=True)), 22 | ('firm', models.ForeignKey(to='sbirez.Firm', related_name='proposals')), 23 | ('owner', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='proposals')), 24 | ('topic', models.ForeignKey(to='sbirez.Topic', related_name='proposals')), 25 | ('workflow', models.ForeignKey(to='sbirez.Workflow', related_name='proposals')), 26 | ], 27 | options={ 28 | }, 29 | bases=(models.Model,), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Welcome! 2 | 3 | We're so glad you're thinking about contributing to an 18F open source project! If you're unsure or afraid of anything, just ask or submit the issue or pull request anyways. The worst that can happen is that you'll be politely asked to change something. We appreciate any sort of contribution, and don't want a wall of rules to get in the way of that. 4 | 5 | Before contributing, we encourage you to read our CONTRIBUTING policy (you are here), our LICENSE, and our README, all of which should be in this repository. If you have any questions, or want to read more about our underlying policies, you can consult the 18F Open Source Policy GitHub repository at https://github.com/18f/open-source-policy, or just shoot us an email/official government letterhead note to [18f@gsa.gov](mailto:18f@gsa.gov). 6 | 7 | ## Public domain 8 | 9 | This project is in the public domain within the United States, and 10 | copyright and related rights in the work worldwide are waived through 11 | the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/). 12 | 13 | All contributions to this project will be released under the CC0 14 | dedication. By submitting a pull request, you are agreeing to comply 15 | with this waiver of copyright interest. -------------------------------------------------------------------------------- /sbirez/migrations/0030_auto_20150817_1927.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0029_auto_20150804_2055'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='PasswordHistory', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)), 19 | ('password', models.CharField(max_length=128)), 20 | ('created_at', models.DateTimeField(auto_now_add=True)), 21 | ], 22 | ), 23 | migrations.AddField( 24 | model_name='sbirezuser', 25 | name='password_expires', 26 | field=models.DateTimeField(null=True), 27 | ), 28 | migrations.RunSQL(sql="UPDATE sbirez_sbirezuser set password_expires=now()+'60 days'"), 29 | migrations.AddField( 30 | model_name='passwordhistory', 31 | name='user', 32 | field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='prior_passwords'), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('MainCtrl', function ($scope, $http, $location, $state, $window, AuthenticationService, DialogService, $rootScope, SearchService) { 5 | $scope.auth = AuthenticationService; 6 | $scope.isLoggedIn = $scope.auth.getAuthenticated() && ($window.sessionStorage.token !== null && $window.sessionStorage.token !== undefined); 7 | $rootScope.bodyClass = 'home'; 8 | $scope.query = ''; 9 | 10 | 11 | AuthenticationService.registerObserverCallback(function() { 12 | $scope.isLoggedIn = AuthenticationService.isAuthenticated && ($window.sessionStorage.token !== null && $window.sessionStorage.token !== undefined && $window.sessionStorage.token !== ''); 13 | }); 14 | 15 | if ($scope.isLoggedIn && $state.includes('home')) { 16 | console.log($state); 17 | $location.path('/~/proposals'); 18 | } else if ($rootScope.preproduction) { 19 | DialogService.openIntroMessage(); 20 | } 21 | 22 | $scope.search = function() { 23 | SearchService.search(1, $scope.query, 10).then(function(data) { 24 | $state.go('search', {'query':$scope.query}, {'reload':true}); 25 | }, function(error) { 26 | console.log(error); 27 | }); 28 | }; 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /sbirez/migrations/0020_auto_20150414_1920.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0019_auto_20150406_0018'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='DocumentVersion', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, primary_key=True, auto_created=True)), 18 | ('note', models.TextField(blank=True, null=True)), 19 | ('created_at', models.DateTimeField(auto_now_add=True)), 20 | ('updated_at', models.DateTimeField(auto_now=True)), 21 | ('file', models.FileField(null=True, upload_to='')), 22 | ], 23 | options={ 24 | 'ordering': ['updated_at'], 25 | }, 26 | ), 27 | migrations.RemoveField( 28 | model_name='document', 29 | name='file', 30 | ), 31 | migrations.AddField( 32 | model_name='documentversion', 33 | name='document', 34 | field=models.ForeignKey(related_name='versions', to='sbirez.Document'), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/documentList.html: -------------------------------------------------------------------------------- 1 |
2 |

Documents

3 |

You currently have {{docList.length}} documents.

4 | 5 | 6 | 7 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
Name 8 | 9 | 10 | Modified 12 | 13 | 14 | Description
{{doc.name}}{{doc.updated_at | date:'medium'}}{{doc.description}}
26 |
27 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/signin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 |
10 | 14 | {{errorEmail}} 15 |
16 |
17 | 21 | {{errorPassword}} 22 |
23 |
24 | {{errorMsg}} 25 |
26 | 30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/topic.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{::topic.title}}

4 |

{{::topic.topic_number}}

5 |

{{::topic.description|truncate:500}}

6 |
7 | 25 |
26 | -------------------------------------------------------------------------------- /tests/client/spec/controllers/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AccountCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('sbirezApp')); 7 | 8 | var AccountCtrl, 9 | $rootScope, 10 | scope, 11 | UserService; 12 | 13 | // Initialize the controller and a mock scope 14 | beforeEach(inject(function ($controller, _$rootScope_, _UserService_) { 15 | UserService = _UserService_; 16 | $rootScope = _$rootScope_; 17 | scope = $rootScope.$new(); 18 | spyOn(UserService, 'getUserDetails').andReturn({'id':1, 'name':'Test User'}); 19 | AccountCtrl = $controller('AccountCtrl', { 20 | $scope: scope 21 | }); 22 | })); 23 | 24 | // This controller no longer retrieves this information. It is done on the user and org page instead 25 | xit('should retrieve the user details from the user service at creation', function () { 26 | expect(UserService.getUserDetails).toHaveBeenCalled(); 27 | expect(scope.user).toBeDefined(); 28 | }); 29 | 30 | xit('should retrieve the user details when userUpdated event is seen', function () { 31 | expect(UserService.getUserDetails).toHaveBeenCalled(); 32 | expect(scope.user).toBeDefined(); 33 | $rootScope.$broadcast('userUpdated'); 34 | expect(UserService.getUserDetails).toHaveBeenCalled(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/accountUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('AccountUserCtrl', function ($scope, $rootScope, UserService) { 5 | $scope.input = {}; 6 | $scope.input.oldpassword = ''; 7 | $scope.input.newpassword1 = ''; 8 | $scope.input.newpassword2 = ''; 9 | $rootScope.bodyClass = 'user'; 10 | 11 | $scope.savePassword = function() { 12 | if ($scope.input.oldpassword !== '' && $scope.input.newpassword1 !== '' && $scope.input.newpassword2 !== '') { 13 | if ($scope.input.newpassword1 === $scope.input.newpassword2) { 14 | UserService.changePassword($scope.input.oldpassword, $scope.input.newpassword1, $scope.input.newpassword2).then(function(data) { 15 | $scope.validationData = {}; 16 | $scope.errorMsg = ''; 17 | $scope.successMsg = 'Password changed successfully.'; 18 | UserService.getUserDetails(); 19 | console.log('password changed'); 20 | }, function(status) { 21 | console.log('password changed', status); 22 | $scope.validationData = status.data; 23 | }); 24 | } else { 25 | $scope.errorMsg = 'New passwords must match.'; 26 | } 27 | } else { 28 | $scope.errorMsg = 'All fields are required.'; 29 | } 30 | }; 31 | }); 32 | -------------------------------------------------------------------------------- /sbirez/static/js/services/dialogsvc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').factory('DialogService', function(ngDialog) { 4 | return { 5 | openLogin : function(intention) { 6 | if (intention) { 7 | console.log(intention); 8 | } 9 | var dialog = ngDialog.open({ 10 | 'template':'static/views/partials/login.html', 11 | 'className':'ngdialog-theme-login', 12 | 'controller':'AdminUserCtrl', 13 | 'data':JSON.stringify(intention) 14 | }); 15 | return dialog.closePromise; 16 | }, 17 | 18 | openLogout : function(intention) { 19 | var dialog = ngDialog.open({ 20 | 'template':'static/views/partials/logout.html', 21 | 'className':'ngdialog-theme-logout', 22 | 'controller':'AdminUserCtrl', 23 | 'data':JSON.stringify(intention) 24 | }); 25 | return dialog.closePromise; 26 | }, 27 | 28 | openIntroMessage : function(intention) { 29 | var dialog = ngDialog.open({ 30 | 'template':'static/views/partials/introMessage.html', 31 | 'className':'ngdialog-theme-intromessage', 32 | 'controller':'AdminUserCtrl', 33 | 'showClose':false, 34 | 'closeByEscape':false, 35 | 'closeByDocument':false, 36 | 'data':JSON.stringify(intention) 37 | }); 38 | return dialog.closePromise; 39 | } 40 | }; 41 | }); 42 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/documentDetails.html: -------------------------------------------------------------------------------- 1 |
2 |

{{data.name}}

3 | Download 4 |
5 |

{{errorMsg}}

6 |

Document successfully updated.

7 |

Last Updated: {{data.updated_at | date:'medium' }}

8 |

Description:

9 | 10 |

Attached to Proposals

11 |
12 | {{proposal.title}} 13 |
14 |

Versions

15 |
16 |

{{version.updated_at | date:'medium' }} Download {{version.note}}

17 |
18 | 19 | 20 | 21 |
22 |
23 |
24 |

File not found

25 |
26 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/introMessage.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Password Required

4 |
5 |
6 |

This system is still under active development and is not intended for public use. All data should be treated as test data.

7 |
8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 |

{{errorMsg}}

21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /sbirez/migrations/0015_auto_20150326_1735.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0014_auto_20150319_1730'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Document', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), 19 | ('name', models.CharField(max_length=255)), 20 | ('description', models.TextField()), 21 | ('file', models.FileField(upload_to='')), 22 | ('created_at', models.DateTimeField(auto_now_add=True)), 23 | ('updated_at', models.DateTimeField(auto_now=True)), 24 | ('firm', models.ForeignKey(to='sbirez.Firm')), 25 | ('proposals', models.ManyToManyField(null=True, to='sbirez.Proposal', blank=True)), 26 | ], 27 | options={ 28 | }, 29 | bases=(models.Model,), 30 | ), 31 | migrations.AlterField( 32 | model_name='topic', 33 | name='fts', 34 | field=djorm_pgfulltext.fields.VectorField(), 35 | preserve_default=True, 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | As a work of the United States Government, this project is in the 2 | public domain within the United States. 3 | 4 | Additionally, we waive copyright and related rights in the work 5 | worldwide through the CC0 1.0 Universal public domain dedication. 6 | 7 | ## CC0 1.0 Universal Summary 8 | 9 | This is a human-readable summary of the 10 | [Legal Code (read the full text)](https://creativecommons.org/publicdomain/zero/1.0/legalcode). 11 | 12 | ### No Copyright 13 | 14 | The person who associated a work with this deed has dedicated the work to 15 | the public domain by waiving all of his or her rights to the work worldwide 16 | under copyright law, including all related and neighboring rights, to the 17 | extent allowed by law. 18 | 19 | You can copy, modify, distribute and perform the work, even for commercial 20 | purposes, all without asking permission. 21 | 22 | ### Other Information 23 | 24 | In no way are the patent or trademark rights of any person affected by CC0, 25 | nor are the rights that other persons may have in the work or in how the 26 | work is used, such as publicity or privacy rights. 27 | 28 | Unless expressly stated otherwise, the person who associated a work with 29 | this deed makes no warranties about the work, and disclaims liability for 30 | all uses of the work, to the fullest extent permitted by applicable law. 31 | When using or citing the work, you should not imply endorsement by the 32 | author or the affirmer. 33 | -------------------------------------------------------------------------------- /sbirez/migrations/0006_auto_20150302_2037.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('sbirez', '0005_auto_20150225_1935'), 10 | ] 11 | 12 | # working around some inadequacies in our django fts module. 13 | # drop/create index is for: https://github.com/djangonauts/djorm-ext-pgfulltext/issues/45 14 | # load fixture loads the data, but the SQL statement after it builds the fts field. 15 | operations = [ 16 | migrations.RunSQL("DROP INDEX IF EXISTS sbirez_topic_fts;"), 17 | migrations.RunSQL("CREATE INDEX sbirez_topic_fts ON sbirez_topic USING gin(fts);"), 18 | migrations.RunSQL("SET search_path=public; WITH subq AS (SELECT t.id, setweight(to_tsvector(string_agg(coalesce(t.title, ''), ' ')), 'A') || setweight(to_tsvector(string_agg(coalesce(t.description, ''), ' ')), 'B') ||setweight(to_tsvector(string_agg(coalesce(a.area, ''), ' ')), 'A') || setweight(to_tsvector(string_agg(coalesce(k.keyword, ''), ' ')), 'A') AS weights FROM sbirez_topic t JOIN sbirez_area_topics ta ON (t.id = ta.topic_id) JOIN sbirez_area a ON (ta.area_id = a.id) JOIN sbirez_keyword_topics tk ON (t.id = tk.topic_id) JOIN sbirez_keyword k ON (tk.keyword_id = k.id) GROUP BY t.id) UPDATE sbirez_topic SET fts = (SELECT weights FROM subq WHERE sbirez_topic.id = subq.id);") 19 | ] 20 | -------------------------------------------------------------------------------- /sbirez/static/css/ngdialog-theme-logout.css: -------------------------------------------------------------------------------- 1 | .ngdialog.ngdialog-theme-logout { 2 | padding-bottom: 160px; 3 | padding-top: 160px; 4 | } 5 | 6 | .ngdialog.ngdialog-theme-logout.ngdialog-closing .ngdialog-content { 7 | -webkit-animation: ngdialog-flyout .5s; 8 | animation: ngdialog-flyout .5s; 9 | } 10 | 11 | .ngdialog.ngdialog-theme-logout .ngdialog-content { 12 | -webkit-animation: ngdialog-flyin .5s; 13 | animation: ngdialog-flyin .5s; 14 | background: #f0f0f0; 15 | border-radius: 5px; 16 | color: #444; 17 | font-family: 'Helvetica',sans-serif; 18 | font-size: 1.1em; 19 | line-height: 1.5em; 20 | margin: 0 auto; 21 | max-width: 100%; 22 | padding: 1em; 23 | position: relative; 24 | width: 35%; 25 | } 26 | 27 | .ngdialog.ngdialog-theme-logout .ngdialog-close { 28 | border-radius: 5px; 29 | cursor: pointer; 30 | position: absolute; 31 | right: 0; 32 | top: 0; 33 | } 34 | 35 | .ngdialog.ngdialog-theme-logout .ngdialog-close:before { 36 | background: transparent; 37 | border-radius: 3px; 38 | color: #bbb; 39 | content: '\00D7'; 40 | font-size: 26px; 41 | font-weight: 400; 42 | height: 30px; 43 | line-height: 26px; 44 | position: absolute; 45 | right: 3px; 46 | text-align: center; 47 | top: 3px; 48 | width: 30px; 49 | } 50 | 51 | .ngdialog.ngdialog-theme-logout .ngdialog-close:hover:before, 52 | .ngdialog.ngdialog-theme-logout .ngdialog-close:active:before { 53 | color: #777; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/search.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Enter a search term above to get started.

5 |
6 |
7 |
8 |

Topics matching {{searchTerm}}

9 |
10 | 11 |
12 |
13 |
14 |
15 |

No Results for {{searchTerm}}

16 |

We couldn’t find any topics matching your search term.

17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /sbirez/static/css/ngdialog-theme-login.css: -------------------------------------------------------------------------------- 1 | .ngdialog.ngdialog-theme-login { 2 | padding-bottom: 160px; 3 | padding-top: 160px; 4 | } 5 | 6 | .ngdialog.ngdialog-theme-login.ngdialog-closing .ngdialog-content { 7 | -webkit-animation: ngdialog-flyout .5s; 8 | animation: ngdialog-flyout .5s; 9 | } 10 | 11 | .ngdialog.ngdialog-theme-login .ngdialog-content { 12 | -webkit-animation: ngdialog-flyin .5s; 13 | animation: ngdialog-flyin .5s; 14 | background: #f0f0f0; 15 | border-radius: 5px; 16 | border: 2px solid #337AB7; 17 | color: #444; 18 | font-family: 'Helvetica',sans-serif; 19 | font-size: 1.1em; 20 | line-height: 1.5em; 21 | margin: 0 auto; 22 | max-width: 100%; 23 | padding: 1em; 24 | position: relative; 25 | width: 400px; 26 | } 27 | 28 | .ngdialog.ngdialog-theme-login .ngdialog-close { 29 | border-radius: 5px; 30 | cursor: pointer; 31 | position: absolute; 32 | right: 0; 33 | top: 0; 34 | } 35 | 36 | .ngdialog.ngdialog-theme-login .ngdialog-close:before { 37 | background: transparent; 38 | border-radius: 3px; 39 | color: #bbb; 40 | content: '\00D7'; 41 | font-size: 26px; 42 | font-weight: 400; 43 | height: 30px; 44 | line-height: 26px; 45 | position: absolute; 46 | right: 3px; 47 | text-align: center; 48 | top: 3px; 49 | width: 30px; 50 | } 51 | 52 | .ngdialog.ngdialog-theme-login .ngdialog-close:hover:before, 53 | .ngdialog.ngdialog-theme-login .ngdialog-close:active:before { 54 | color: #777; 55 | } 56 | -------------------------------------------------------------------------------- /sbirez/static/images/svg/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /sbirez/management/commands/indextopics.py: -------------------------------------------------------------------------------- 1 | from django.db import connection, transaction 2 | from django.core.management.base import BaseCommand, CommandError 3 | 4 | class Command(BaseCommand): 5 | args = '' 6 | help = 'Updates the full text search index for topics' 7 | 8 | def handle(self, *args, **options): 9 | cursor = connection.cursor() 10 | cursor.execute('''\ 11 | WITH subq AS ( 12 | SELECT t.id, 13 | setweight(to_tsvector('english', string_agg(coalesce(t.title, ''), ' ')), 'A') || 14 | setweight(to_tsvector('english', string_agg(coalesce(t.description, ''), ' ')), 'B') || 15 | setweight(to_tsvector('english', string_agg(coalesce(a.area, ''), ' ')), 'A') || 16 | setweight(to_tsvector('english', string_agg(coalesce(k.keyword, ''), ' ')), 'A') 17 | AS weights 18 | FROM sbirez_topic t 19 | LEFT JOIN sbirez_area_topics ta ON (t.id = ta.topic_id) 20 | LEFT JOIN sbirez_area a ON (ta.area_id = a.id) 21 | LEFT JOIN sbirez_keyword_topics tk ON (t.id = tk.topic_id) 22 | LEFT JOIN sbirez_keyword k ON (tk.keyword_id = k.id) 23 | GROUP BY t.id) 24 | UPDATE sbirez_topic 25 | SET fts = (SELECT weights FROM subq 26 | WHERE sbirez_topic.id = subq.id);\ 27 | ''') 28 | #transaction.set_dirty() 29 | transaction.commit() 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['ng-scenario'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'test/client/e2e/**/*.js' 15 | ], 16 | 17 | // list of files / patterns to exclude 18 | exclude: [], 19 | 20 | // web server port 21 | port: 8080, 22 | 23 | // level of logging 24 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 25 | logLevel: config.LOG_INFO, 26 | 27 | 28 | // enable / disable watching file and executing tests whenever any file changes 29 | autoWatch: false, 30 | 31 | 32 | // Start these browsers, currently available: 33 | // - Chrome 34 | // - ChromeCanary 35 | // - Firefox 36 | // - Opera 37 | // - Safari (only Mac) 38 | // - PhantomJS 39 | // - IE (only Windows) 40 | browsers: ['Chrome'], 41 | 42 | 43 | // Continuous Integration mode 44 | // if true, it capture browsers, run tests and exit 45 | singleRun: false 46 | 47 | // Uncomment the following lines if you are using grunt's server to run the tests 48 | // proxies: { 49 | // '/': 'http://localhost:9000/' 50 | // }, 51 | // URL root prevent conflicts with the site root 52 | // urlRoot: '_karma_' 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /sbirez/migrations/0032_auto_20150910_0836.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sbirez', '0031_auto_20150828_1751'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='PointOfContactRelationship', 16 | fields=[ 17 | ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), 18 | ('order', models.IntegerField()), 19 | ], 20 | options={ 21 | 'ordering': ['order'], 22 | }, 23 | ), 24 | migrations.AddField( 25 | model_name='person', 26 | name='office', 27 | field=models.TextField(null=True, blank=True), 28 | ), 29 | migrations.AddField( 30 | model_name='pointofcontactrelationship', 31 | name='poc', 32 | field=models.ForeignKey(to='sbirez.Person'), 33 | ), 34 | migrations.AddField( 35 | model_name='pointofcontactrelationship', 36 | name='topic', 37 | field=models.ForeignKey(to='sbirez.Topic'), 38 | ), 39 | migrations.AddField( 40 | model_name='topic', 41 | name='tech_points_of_contact', 42 | field=models.ManyToManyField(through='sbirez.PointOfContactRelationship', related_name='topics', blank=True, to='sbirez.Person'), 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /sbirez/management/commands/importtopics.py: -------------------------------------------------------------------------------- 1 | from .modules.copy_topic_csvs import load 2 | from sbirez.models import Solicitation 3 | from django.core.management.base import BaseCommand 4 | from django.core.management import call_command, CommandError 5 | from django.conf import settings 6 | import subprocess 7 | import os 8 | 9 | class Command(BaseCommand): 10 | help = """Populates Topics with an .mdb dump. 11 | 12 | Usage: python manage.py importtopics "" 13 | (solicitation name e.g. "DoD SBIR 2015.1"; use quotation marks) 14 | """ 15 | 16 | def add_arguments(self, parser): 17 | 18 | parser.add_argument('mdb_filename') 19 | parser.add_argument('solicitation_name') 20 | # example: DoD SBIR 2015.1 21 | parser.add_argument('--clear', action='store_true', 22 | default=False, 23 | help='First, delete all records with this solicitation') 24 | 25 | def handle(self, mdb_filename, solicitation_name, clear=False, **options): 26 | 27 | if not os.path.isfile(mdb_filename): 28 | raise OSError("MS Access database %s not found" % mdb_filename) 29 | 30 | # Use MDB tools to dump CSVs from MS Access .mdb file 31 | os.system("mdb-export %s commands > data/command.csv" % mdb_filename) 32 | os.system("mdb-export %s agency > data/agency.csv" % mdb_filename) 33 | os.system("mdb-export %s topics > data/topic.csv" % mdb_filename) 34 | 35 | load(solicitation_name, clear=clear) 36 | 37 | call_command('indextopics') 38 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/dynamiclineitem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('dynamiclineitem', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | dynamiclineitem: '=', 9 | proposal: '@' 10 | }, 11 | templateUrl: 'static/views/partials/elements/dynamiclineitem.html', 12 | controller: ['$scope', 'ProposalService', 13 | function ($scope, ProposalService) { 14 | $scope.element = $scope.dynamiclineitem; 15 | $scope.visible = true; 16 | $scope.visibleCount = $scope.element.multiplicity.length; 17 | 18 | if ($scope.element.multiplicityCount) { 19 | $scope.visibleCount = Math.max(1, ProposalService.getDynamicCount($scope.element)); 20 | } 21 | 22 | var askIfCallback = function(data) { 23 | $scope.visible = (data === true || data === 'true'); 24 | }; 25 | $scope.storage = ProposalService.register($scope.element, 26 | null, 27 | $scope.element.ask_if !== null ? askIfCallback : null, 28 | $scope.multipletoken); 29 | 30 | $scope.addAnother = function() { 31 | ProposalService.addDynamicItem($scope.element); 32 | $scope.visibleCount++; 33 | }; 34 | 35 | $scope.remove = function(index) { 36 | console.log('index', index); 37 | ProposalService.removeDynamicItem($scope.element, index); 38 | $scope.visibleCount--; 39 | }; 40 | } 41 | ] 42 | }; 43 | }); 44 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/topic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('topic', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | topic: '=', 9 | viewDetails: '@', 10 | removeOption: '@', 11 | createOption: '@' 12 | }, 13 | templateUrl: 'static/views/partials/topic.html', 14 | controller: ['$scope', '$state', 'SavedOpportunityService', 'ProposalService', 15 | function ($scope, $state, SavedOpportunityService, ProposalService) { 16 | 17 | $scope.saveOpportunity = function() { 18 | SavedOpportunityService.save($scope.topic.id).then(function() { 19 | $scope.topic.saved = true; 20 | }, function(error) { 21 | console.log(error); 22 | }); 23 | }; 24 | 25 | $scope.removeOpportunity = function() { 26 | SavedOpportunityService.remove($scope.topic.id).then(function() { 27 | $scope.topic = {}; 28 | }); 29 | }; 30 | 31 | $scope.createProposal = function() { 32 | var title = 'Proposal for ' + $scope.topic.title; 33 | var workflow = $scope.topic.solicitation.element; 34 | SavedOpportunityService.save($scope.topic.id).then(function() { 35 | ProposalService.create($scope.topic.id, title, workflow).then(function(data) { 36 | $scope.topic.proposal = data.id; 37 | $state.go('app.proposals.report', {id: $scope.topic.proposal}); 38 | }); 39 | }); 40 | }; 41 | 42 | } 43 | ] 44 | }; 45 | }); 46 | -------------------------------------------------------------------------------- /sbirez/migrations/0015_auto_20150327_1426.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0014_auto_20150319_1730'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Element', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), 19 | ('name', models.TextField()), 20 | ('human', models.TextField(null=True, blank=True)), 21 | ('element_type', models.TextField(default='str')), 22 | ('order', models.IntegerField()), 23 | ('multiplicity', models.TextField(null=True, blank=True)), 24 | ('required', models.NullBooleanField(default=False)), 25 | ('default', models.TextField(null=True, blank=True)), 26 | ('help', models.TextField(null=True, blank=True)), 27 | ('validation', models.TextField(null=True, blank=True)), 28 | ('validation_msg', models.TextField(null=True, blank=True)), 29 | ('ask_if', models.TextField(null=True, blank=True)), 30 | ('parent', models.ForeignKey(null=True, related_name='children', to='sbirez.Element')), 31 | ], 32 | options={ 33 | 'ordering': ['order'], 34 | }, 35 | bases=(models.Model,), 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /tests/client/spec/controllers/documentList.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: DocumentListCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('sbirezApp')); 7 | 8 | var DocListCtrl, 9 | scope, 10 | $httpBackend, 11 | $window, 12 | $q, 13 | $rootScope, 14 | AuthenticationService, 15 | DocumentService; 16 | 17 | // Initialize the controller and a mock scope 18 | beforeEach(inject(function (_$httpBackend_, $controller, _$rootScope_, _$window_, _$q_, _AuthenticationService_, _DocumentService_) { 19 | $httpBackend = _$httpBackend_; 20 | $window = _$window_; 21 | $q = _$q_; 22 | $rootScope = _$rootScope_; 23 | AuthenticationService = _AuthenticationService_; 24 | DocumentService = _DocumentService_; 25 | $httpBackend.whenGET('static/views/partials/main.html').respond({}); 26 | scope = $rootScope.$new(); 27 | spyOn(DocumentService, 'list').andCallFake(function() { 28 | var deferred = $q.defer(); 29 | deferred.resolve({'results':[ 30 | {'name':'file1','id':1}, 31 | {'name':'file2','id':2}, 32 | {'name':'file3','id':3} 33 | ]}); 34 | return deferred.promise; 35 | }); 36 | DocListCtrl = $controller('DocumentListCtrl', { 37 | $scope: scope 38 | }); 39 | })); 40 | 41 | it('should attach a list of documents to the scope', function () { 42 | $window.sessionStorage.userid = 1; 43 | AuthenticationService.setAuthenticated(true); 44 | expect(scope.docList.length).toBe(0); 45 | $rootScope.$digest(); 46 | expect(scope.docList.length).toBe(3); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /afsbirez/settings/production.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: CONFIGURE 3 | 4 | Production settings for afsbirez project. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.7/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.7/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 16 | 17 | from .base import * 18 | from .credentials import EMAIL_HOST, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, DJANGO_SECRET_KEY 19 | from .credentials import SAM_API_KEY 20 | 21 | import dj_database_url 22 | 23 | # Quick-start development settings - unsuitable for production 24 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ 25 | 26 | # SECURITY WARNING: keep the secret key used in production secret! 27 | SECRET_KEY = DJANGO_SECRET_KEY 28 | 29 | # SECURITY WARNING: don't run with debug turned on in production! 30 | DEBUG = False 31 | 32 | TEMPLATE_DEBUG = False 33 | 34 | ALLOWED_HOSTS = ['sbirez.cf.18f.us', 'sbirez.18f.gov'] 35 | 36 | DATABASES = {"default": dj_database_url.config()} 37 | 38 | DJMAIL_REAL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 39 | EMAIL_PORT = 465 40 | EMAIL_USE_SSL = True 41 | # store email credentials in `credentials.py` (outside version control) 42 | 43 | REST_PROXY['API_KEY'] = SAM_API_KEY 44 | 45 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 46 | 47 | AUTH_PASSWORD_HISTORY_COUNT = 10 48 | AUTH_PASSWORD_LENGTH = 12 49 | AUTH_PASSWORD_EXPIRATION_DAYS = 60 50 | AUTH_PASSWORD_DIFFERENCE = 4 51 | 52 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/topic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('TopicCtrl', function ($scope, $rootScope, $http, $state, $window, AuthenticationService, SavedOpportunityService, ProposalService) { 5 | $scope.topicId = $state.params.id; 6 | $rootScope.bodyClass = 'topic-show'; 7 | $scope.data = {}; 8 | 9 | $scope.createProposal = function() { 10 | var title = 'Proposal for ' + $scope.data.title; 11 | var workflow = $scope.data.solicitation.element; 12 | SavedOpportunityService.save($scope.topicId).then(function() { 13 | $scope.data.saved = true; 14 | ProposalService.create($scope.data.id, title, workflow).then(function(data) { 15 | $scope.data.proposal = data.id; 16 | $state.go('app.proposals.report', {id: $scope.data.proposal}); 17 | }); 18 | }); 19 | }; 20 | 21 | $scope.getKeywordList = function() { 22 | var keywords = ''; 23 | if ($scope.data.keywords && $scope.data.keywords[0]) { 24 | keywords = $scope.data.keywords[0].keyword; 25 | var length = $scope.data.keywords.length; 26 | for (var i = 1; i < length; i++) { 27 | keywords += ', ' + $scope.data.keywords[i].keyword; 28 | } 29 | } 30 | return keywords; 31 | }; 32 | 33 | $scope.back = function() { 34 | $window.history.back(); 35 | }; 36 | 37 | $http.get('api/v1/topics/' + $scope.topicId + '/').success(function(data) { 38 | $scope.data = data; 39 | }).error(function(/*data, status, headers, config*/) { 40 | $scope.errorMsg = 'The topic you are looking for does not exist.'; 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /sbirez/migrations/0031_auto_20150828_1751.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0030_auto_20150817_1927'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='area', 17 | name='topics', 18 | field=models.ManyToManyField(blank=True, to='sbirez.Topic', related_name='areas'), 19 | ), 20 | migrations.AlterField( 21 | model_name='document', 22 | name='proposals', 23 | field=models.ManyToManyField(blank=True, to='sbirez.Proposal'), 24 | ), 25 | migrations.AlterField( 26 | model_name='jargon', 27 | name='elements', 28 | field=models.ManyToManyField(blank=True, to='sbirez.Element', related_name='jargons'), 29 | ), 30 | migrations.AlterField( 31 | model_name='keyword', 32 | name='topics', 33 | field=models.ManyToManyField(blank=True, to='sbirez.Topic', related_name='keywords'), 34 | ), 35 | migrations.AlterField( 36 | model_name='naics', 37 | name='firms', 38 | field=models.ManyToManyField(blank=True, to='sbirez.Firm', related_name='naics'), 39 | ), 40 | migrations.AlterField( 41 | model_name='topic', 42 | name='saved_by', 43 | field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, related_name='saved_topics'), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/bool.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('bool', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | bool: '=', 9 | multiplename: '=?', 10 | multipletoken: '=?' 11 | }, 12 | templateUrl: 'static/views/partials/elements/bool.html', 13 | controller: ['$scope', 'ProposalService', 14 | function ($scope, ProposalService) { 15 | $scope.element = $scope.bool; 16 | $scope.visible = true; 17 | $scope.validationstorage = ''; 18 | 19 | var validationCallback = function(data) { 20 | $scope.validationstorage = data; 21 | }; 22 | 23 | var askIfCallback = function(data) { 24 | $scope.visible = (data === true || data === 'true'); 25 | }; 26 | 27 | $scope.storage = ProposalService.register($scope.element, 28 | validationCallback, 29 | $scope.element.ask_if !== null ? askIfCallback : null, 30 | $scope.multipleName); 31 | 32 | $scope.fieldName = $scope.element.human; 33 | if ($scope.multiplename !== undefined && $scope.element.human.indexOf('%multiple%') > -1) { 34 | $scope.fieldName = $scope.element.human.replace('%multiple%', $scope.multiplename); 35 | } 36 | 37 | $scope.fieldToken = $scope.element.name; 38 | if ($scope.multipletoken !== undefined) { 39 | $scope.fieldToken = $scope.element.name + '_' + $scope.multipletoken; 40 | } 41 | 42 | $scope.apply = function() { 43 | ProposalService.apply($scope.element, $scope.storage); 44 | }; 45 | } 46 | ] 47 | }; 48 | }); 49 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/pagination.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('pagination', ['$compile', function($compile) { 4 | return { 5 | restrict: 'EA', 6 | replace: false, 7 | scope: { 8 | 'itemCount': '@', 9 | 'itemsPerPage': '@', 10 | 'method': '@', 11 | 'currentPage': '@' 12 | }, 13 | link: function (scope, element, attrs) { 14 | attrs.$observe('itemCount', function() { 15 | updatePagination(); 16 | }); 17 | attrs.$observe('currentPage', function() { 18 | updatePagination(); 19 | }); 20 | 21 | function updatePagination() { 22 | var currentPage = parseInt(scope.currentPage); 23 | var itemsPerPage = parseInt(scope.itemsPerPage); 24 | var itemCount = parseInt(scope.itemCount); 25 | 26 | var startRange = currentPage * itemsPerPage - (itemsPerPage - 1); 27 | var endRange = Math.min(currentPage * itemsPerPage, itemCount); 28 | 29 | var retVal = ''; 37 | 38 | element.html(retVal); 39 | $compile(element.contents())(scope); 40 | } 41 | 42 | updatePagination(); 43 | } 44 | }; 45 | }]); 46 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/accountUser.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Change Password

5 |
6 | 9 | {{validationData.old_password[0]}} 10 |
11 |
12 | 15 | {{validationData.new_password1[0]}} 16 |
17 |
18 | 21 | {{validationData.new_password2[0]}} 22 |
23 |
24 | {{errorMsg}} 25 |
26 |
27 | {{successMsg}} 28 |
29 | 30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/document.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('DocumentCtrl', function ($scope, $http, $state, $window, $location, DocumentService, ProposalService) { 5 | $scope.documentId = $state.params.id; 6 | $scope.jwt = $window.sessionStorage.token; 7 | $scope.errorMsg = ''; 8 | $scope.updated = false; 9 | $scope.proposals = []; 10 | $scope.versions = []; 11 | 12 | var pushProposal = function(data) { 13 | $scope.proposals.push(data); 14 | }; 15 | 16 | var pushVersion = function(data) { 17 | $scope.versions.push(data); 18 | }; 19 | 20 | DocumentService.get(parseInt($scope.documentId)).then(function(data) { 21 | $scope.data = data; 22 | if ($scope.data.proposals !== undefined) { 23 | for (var i = 0; i < $scope.data.proposals.length; i++) { 24 | ProposalService.get($scope.data.proposals[i]).then(pushProposal); 25 | } 26 | } 27 | if ($scope.data.versions !== undefined) { 28 | for (var j = 0; j < $scope.data.versions.length; j++) { 29 | DocumentService.getVersion($scope.data.versions[j]).then(pushVersion); 30 | } 31 | } 32 | }); 33 | 34 | $scope.save = function() { 35 | DocumentService.saveData(parseInt($scope.documentId), {'description':$scope.data.description}).then(function() { 36 | $scope.updated = true; 37 | }, function(data) { 38 | $scope.errorMsg = data; 39 | $scope.updated = false; 40 | }); 41 | }; 42 | 43 | $scope.remove = function() { 44 | DocumentService.remove(parseInt($scope.documentId)).then(function() { 45 | console.log('file removed...need to redirect.'); 46 | $location.path('/app/documents'); 47 | }); 48 | }; 49 | }); 50 | -------------------------------------------------------------------------------- /sbirez/migrations/0009_auto_20150313_1659.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0008_auto_20150304_2134'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='topic', 17 | name='fts', 18 | field=djorm_pgfulltext.fields.VectorField(null=True, editable=False, default='', db_index=True, serialize=False), 19 | preserve_default=True, 20 | ), 21 | ] 22 | 23 | # working around some inadequacies in our django fts module. 24 | # drop/create index is for: https://github.com/djangonauts/djorm-ext-pgfulltext/issues/45 25 | # load fixture loads the data, but the SQL statement after it builds the fts field. 26 | operations = [ 27 | migrations.RunSQL("DROP INDEX IF EXISTS sbirez_topic_fts;"), 28 | migrations.RunSQL("CREATE INDEX sbirez_topic_fts ON sbirez_topic USING gin(fts);"), 29 | migrations.RunSQL("SET search_path=public; WITH subq AS (SELECT t.id, setweight(to_tsvector(string_agg(coalesce(t.title, ''), ' ')), 'A') || setweight(to_tsvector(string_agg(coalesce(t.description, ''), ' ')), 'B') ||setweight(to_tsvector(string_agg(coalesce(a.area, ''), ' ')), 'A') || setweight(to_tsvector(string_agg(coalesce(k.keyword, ''), ' ')), 'A') AS weights FROM sbirez_topic t JOIN sbirez_area_topics ta ON (t.id = ta.topic_id) JOIN sbirez_area a ON (ta.area_id = a.id) JOIN sbirez_keyword_topics tk ON (t.id = tk.topic_id) JOIN sbirez_keyword k ON (tk.keyword_id = k.id) GROUP BY t.id) UPDATE sbirez_topic SET fts = (SELECT weights FROM subq WHERE sbirez_topic.id = subq.id);") 30 | ] -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/checkbox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('checkbox', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | checkbox: '=', 9 | multiplename: '=?', 10 | multipletoken: '=?' 11 | }, 12 | templateUrl: 'static/views/partials/elements/checkbox.html', 13 | controller: ['$scope', 'ProposalService', 14 | function ($scope, ProposalService) { 15 | $scope.element = $scope.checkbox; 16 | $scope.fieldName = $scope.element.human; 17 | $scope.visible = true; 18 | if ($scope.multiplename !== undefined && $scope.element.human.indexOf('%multiple%') > -1) { 19 | $scope.fieldName = $scope.element.human.replace('%multiple%', $scope.multiplename); 20 | } 21 | 22 | $scope.fieldToken = $scope.element.name; 23 | if ($scope.multipletoken !== undefined) { 24 | $scope.fieldToken = $scope.element.name + '_' + $scope.multipletoken; 25 | } 26 | 27 | $scope.validationstorage = ''; 28 | 29 | var validationCallback = function(data) { 30 | $scope.validationstorage = data; 31 | }; 32 | 33 | var askIfCallback = function(data) { 34 | $scope.visible = (data === true || data === 'true'); 35 | }; 36 | 37 | $scope.storage = ProposalService.register($scope.element, 38 | validationCallback, 39 | $scope.element.ask_if !== null ? askIfCallback : null, 40 | $scope.multipletoken); 41 | 42 | $scope.apply = function() { 43 | ProposalService.apply($scope.element, $scope.storage, $scope.multipletoken); 44 | }; 45 | } 46 | ] 47 | }; 48 | }); 49 | -------------------------------------------------------------------------------- /data/holygrail.yaml: -------------------------------------------------------------------------------- 1 | - 2 | name: holy_grail_workflow 3 | element_type: workflow 4 | children: 5 | - 6 | name: quest_thy_name 7 | human: What is thy name? 8 | element_type: med_str 9 | required: True 10 | help: Thy moniker 11 | - 12 | name: subquest 13 | element_type: group 14 | children: 15 | - 16 | name: quest_thy_quest 17 | human: What is thy quest? 18 | element_type: med_str 19 | - 20 | name: quest_thy_favorite_color 21 | human: What is thy favorite color? 22 | required: True 23 | element_type: med_str 24 | validation: does_not_equal blue 25 | validation_msg: Lancelot already said blue 26 | - 27 | name: knights 28 | element_type: line_item 29 | required: False 30 | multiplicity: Lancelot, Galahad, Robin 31 | children: 32 | - 33 | name: is_courageous 34 | element_type: bool 35 | - 36 | name: how_courageous_exactly 37 | human: "On a scale of 1 to 10, how courageous?" 38 | ask_if: is_courageous 39 | required: True 40 | element_type: int 41 | jargons: 42 | - 43 | name: courage 44 | html: "the ability to do something that frightens one" 45 | - 46 | name: minstrels 47 | element_type: line_item 48 | required: False 49 | multiplicity: 3 50 | children: 51 | - 52 | name: name 53 | element_type: med_str 54 | - 55 | name: instrument 56 | element_type: med_str 57 | - 58 | name: kg 59 | human: weight in kg 60 | required: True 61 | element_type: float 62 | validation: not_less_than 0 63 | - 64 | name: lb 65 | human: weight in lb 66 | required: True 67 | element_type: float 68 | validation: "kg / 2.20462" 69 | -------------------------------------------------------------------------------- /sbirez/management/commands/readworkflow.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import logging 3 | 4 | from django.core.management.base import BaseCommand, CommandError 5 | from sbirez.models import * 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | class Command(BaseCommand): 10 | args = 'file_name' 11 | help = 'Loads workflow elements from a yaml file' 12 | 13 | def _save_element(self, element_dict, parent=None): 14 | fields = dict(element_dict) 15 | if parent: 16 | fields['parent_id'] = parent.id 17 | children = fields.pop('children', []) 18 | jargons = fields.pop('jargons', []) 19 | instance = Element(**fields) 20 | instance.save() 21 | for jargon in jargons: 22 | try: 23 | jargon_instance = Jargon.objects.get(name=jargon['name']) 24 | if (('html' in jargon) and 25 | (jargon['html'] != jargon_instance.html)): 26 | logger.warn( 27 | 'HTML for jargon `%s` already specified, ignoring edit' 28 | % jargon_instance.name) 29 | except Jargon.DoesNotExist: 30 | jargon_instance = Jargon(name=jargon['name'], 31 | html=jargon['html']) 32 | jargon_instance.save() 33 | instance.jargons.add(jargon_instance) 34 | 35 | order = 1 36 | for child in children: 37 | child['order'] = order 38 | self._save_element(child, parent=instance) 39 | order += 1 40 | 41 | def handle(self, *args, **options): 42 | with open(args[0]) as infile: 43 | allelements = yaml.load(infile) 44 | order = 1 45 | for el in allelements: 46 | el['order'] = order 47 | self._save_element(el) 48 | order += 1 49 | -------------------------------------------------------------------------------- /sbirez/static/css/reset.css: -------------------------------------------------------------------------------- 1 | /* @override http://localhost:4000/stylesheets/reset.css */ 2 | 3 | /* http://meyerweb.com/eric/tools/css/reset/ 4 | v2.0 | 20110126 5 | License: none (public domain) 6 | */ 7 | 8 | html, body, div, span, applet, object, iframe, 9 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 10 | a, abbr, acronym, address, big, cite, code, 11 | del, dfn, em, img, ins, kbd, q, s, samp, 12 | small, strike, strong, sub, sup, tt, var, 13 | b, u, i, 14 | dl, dt, dd, ol, ul, li, 15 | fieldset, form, label, legend, 16 | table, caption, tbody, tfoot, thead, tr, th, td, 17 | article, aside, canvas, details, embed, 18 | figure, figcaption, footer, header, menu, nav, 19 | output, ruby, section, summary, 20 | time, mark, audio, video { 21 | margin: 0; 22 | padding: 0; 23 | border: 0; 24 | font-size: 100%; 25 | font: inherit; 26 | vertical-align: baseline; 27 | } 28 | /* HTML5 display-role reset for older browsers */ 29 | article, aside, details, figcaption, figure, 30 | footer, header, hgroup, menu, nav, section { 31 | display: block; 32 | } 33 | body { line-height: 1; } 34 | ol, ul { list-style: none; } 35 | blockquote, q { quotes: none; } 36 | blockquote:before, blockquote:after, 37 | q:before, q:after { content: ''; content: none; } 38 | table { 39 | border-collapse: collapse; 40 | border-spacing: 0; 41 | } 42 | 43 | html { 44 | box-sizing: border-box; 45 | font-size: 16px; 46 | font-family: sans-serif; /* 1 */ 47 | -ms-text-size-adjust: 100%; 48 | -webkit-text-size-adjust: 100%; /* Prevent font scaling in landscape while allowing user zoom */ 49 | } 50 | 51 | *, *:before, *:after { box-sizing: inherit; } 52 | 53 | html{ font-size: 16px; } 54 | 55 | sub, 56 | sup { 57 | font-size: 75%; 58 | line-height: 0; 59 | position: relative; 60 | vertical-align: baseline; 61 | } 62 | 63 | sup { top: -0.5em; } 64 | sub { bottom: -0.25em; } -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('text', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | text: '=', 9 | multiplename: '=?', 10 | multipletoken: '=?' 11 | }, 12 | templateUrl: 'static/views/partials/elements/text.html', 13 | controller: ['$scope', 'ProposalService', 14 | function ($scope, ProposalService) { 15 | $scope.element = $scope.text; 16 | $scope.validationstorage = ''; 17 | $scope.visible = true; 18 | 19 | var validationCallback = function(data) { 20 | $scope.validationstorage = data; 21 | }; 22 | 23 | var askIfCallback = function(data) { 24 | $scope.visible = (data === true || data === 'true'); 25 | }; 26 | 27 | $scope.storage = ProposalService.register($scope.element, 28 | validationCallback, 29 | $scope.element.ask_if !== null ? askIfCallback : null, 30 | $scope.multipletoken); 31 | if ($scope.storage.length === undefined) { 32 | $scope.storage = ''; 33 | } 34 | 35 | $scope.fieldName = $scope.element.human; 36 | if ($scope.multiplename !== undefined && $scope.element.human.indexOf('%multiple%') > -1) { 37 | $scope.fieldName = $scope.element.human.replace('%multiple%', $scope.multiplename); 38 | } 39 | 40 | $scope.fieldToken = $scope.element.name; 41 | if ($scope.multipletoken !== undefined) { 42 | $scope.fieldToken = $scope.element.name + '_' + $scope.multipletoken; 43 | } 44 | 45 | $scope.apply = function() { 46 | ProposalService.apply($scope.element, $scope.storage, $scope.multipletoken); 47 | }; 48 | } 49 | ] 50 | }; 51 | }); 52 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/signup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 |
10 | 14 | {{errorName}} 15 |
16 |
17 | 21 | {{errorEmail}} 22 |
23 |
24 | 33 |
34 |
35 | {{errorMsg}} 36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/jargon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('jargon', function(){ 4 | return { 5 | restrict: 'AE', 6 | scope: { 7 | jargon: '=' 8 | }, 9 | link: function (scope, element, attrs) { 10 | 11 | function spliceSlice(str, index, count, add) { 12 | return str.slice(0, index) + (add || '') + str.slice(index + count); 13 | } 14 | 15 | function set(val) { 16 | var found = false; 17 | var startIndex, endIndex; 18 | startIndex = val.indexOf('',startIndex); 21 | var id = val.substr(startIndex + 8, endIndex - startIndex - 8); 22 | for (var i = 0; i < scope.jargon.jargons.length; i++) { 23 | if (scope.jargon.jargons[i].name === id) { 24 | found = true; 25 | val = spliceSlice(val, startIndex, endIndex - startIndex + 1, 26 | '' + i + '' + 27 | '
  1. ' + 28 | scope.jargon.jargons[i].html + 29 | 'R
'); 30 | break; 31 | } 32 | } 33 | if (!found) { 34 | console.log('ERROR: jargon not found', id); 35 | val = spliceSlice(val, startIndex, endIndex - startIndex + 1, ''); 36 | } else { 37 | found = false; 38 | } 39 | startIndex = val.indexOf(' 0) { 19 | $rootScope.bodyClass = 'topics'; 20 | } else { 21 | $rootScope.bodyClass = 'topics-no-results'; 22 | } 23 | }; 24 | 25 | $scope.search = function(page) { 26 | SearchService.search(page, $scope.searchTerm, $scope.itemsPerPage).then(function(data) { 27 | $scope.results = data; 28 | if (data !== undefined && data.results !== undefined) { 29 | $scope.itemCount = data.results.length; 30 | $scope.numFound = data.count; 31 | $scope.simpleMode = false; 32 | } 33 | }, function(error) { 34 | console.log(error); 35 | }); 36 | $scope.currentPage = SearchService.getPage(); 37 | $location.search({'query': $scope.searchTerm, 'page': $scope.currentPage}); 38 | $anchorScroll(); 39 | }; 40 | 41 | loadState(); 42 | 43 | var query = $location.search(); 44 | if (query && query.query && ($scope.searchTerm !== query.query || $scope.numFound === 0)) { 45 | $scope.searchTerm = query.query; 46 | if (query.page) { 47 | $scope.currentPage = query.page; 48 | } 49 | $scope.search($scope.currentPage); 50 | } 51 | 52 | $scope.simpleMode = ($scope.searchTerm === ''); 53 | $scope.simpleModeIcebox = true; 54 | 55 | SearchService.registerObserverCallback(loadState); 56 | 57 | $scope.clear = function() { 58 | SearchService.clearState(); 59 | loadState(); 60 | }; 61 | }); 62 | -------------------------------------------------------------------------------- /tests/client/spec/controllers/reset.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ResetCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('sbirezApp')); 7 | 8 | var ResetCtrl, 9 | scope, 10 | window, 11 | UserService, 12 | $httpBackend, 13 | $location, 14 | $q; 15 | 16 | // Initialize the controller and a mock scope 17 | beforeEach(inject(function (_$httpBackend_, _$q_, _$location_, $controller, $rootScope, $window, _UserService_) { 18 | $httpBackend = _$httpBackend_; 19 | $httpBackend.whenGET('static/views/partials/main.html').respond({}); 20 | UserService = _UserService_; 21 | scope = $rootScope.$new(); 22 | window = $window; 23 | $q = _$q_; 24 | $location = _$location_; 25 | ResetCtrl = $controller('ResetCtrl', { 26 | $scope: scope 27 | }); 28 | })); 29 | 30 | it('should accept an email and not set an error on success', function () { 31 | var mockDeferred = $q.defer(); 32 | spyOn(UserService, 'resetPassword').andReturn(mockDeferred.promise); 33 | scope.email = 'test@user.com'; 34 | scope.reset(); 35 | mockDeferred.resolve({'data':{"success":"Password reset e-mail has been sent."}}); 36 | scope.$root.$digest(); 37 | expect(scope.errorMsg).toBe(''); 38 | expect(scope.successMsg).toBe('Password reset e-mail has been sent.'); 39 | }); 40 | 41 | it('should not accept an email and set an error on failure', function () { 42 | var mockDeferred = $q.defer(); 43 | spyOn(UserService, 'resetPassword').andReturn(mockDeferred.promise); 44 | scope.email = 'test@user.com'; 45 | scope.reset(); 46 | mockDeferred.reject({}); 47 | scope.$root.$digest(); 48 | expect(scope.errorMsg).toBe('Unable to reset password.'); 49 | }); 50 | 51 | it('should require an email address', function () { 52 | var mockDeferred = $q.defer(); 53 | spyOn(UserService, 'resetPassword').andReturn(mockDeferred.promise); 54 | scope.email = ''; 55 | scope.reset(); 56 | mockDeferred.resolve({}); 57 | scope.$root.$digest(); 58 | expect(scope.errorMsg).toBe('Please provide an email address.'); 59 | }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /sbirez/migrations/0011_auto_20150317_1630.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0010_auto_20150312_2041'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Question', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), 19 | ('name', models.TextField()), 20 | ('order', models.IntegerField()), 21 | ('data_type', models.TextField(default='str')), 22 | ('required', models.BooleanField(default=False)), 23 | ('human', models.TextField(blank=True)), 24 | ('help', models.TextField(blank=True)), 25 | ('validation', models.TextField(blank=True)), 26 | ('ask_if', models.TextField(blank=True)), 27 | ], 28 | options={ 29 | 'ordering': ['order'], 30 | }, 31 | bases=(models.Model,), 32 | ), 33 | migrations.CreateModel( 34 | name='Workflow', 35 | fields=[ 36 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), 37 | ('name', models.TextField()), 38 | ('validation', models.TextField()), 39 | ], 40 | options={ 41 | }, 42 | bases=(models.Model,), 43 | ), 44 | migrations.AddField( 45 | model_name='question', 46 | name='parent', 47 | field=models.ForeignKey(to='sbirez.Workflow', related_name='questions'), 48 | preserve_default=True, 49 | ), 50 | migrations.AddField( 51 | model_name='question', 52 | name='subworkflow', 53 | field=models.ForeignKey(blank=True, to='sbirez.Workflow', null=True, related_name='subworkflow_of'), 54 | preserve_default=True, 55 | ), 56 | ] 57 | -------------------------------------------------------------------------------- /sbirez/migrations/0005_auto_20150225_1935.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0004_auto_20150223_2159'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='area', 17 | name='topics', 18 | field=models.ManyToManyField(null=True, blank=True, related_name='areas', to='sbirez.Topic'), 19 | preserve_default=True, 20 | ), 21 | migrations.AlterField( 22 | model_name='keyword', 23 | name='topics', 24 | field=models.ManyToManyField(null=True, blank=True, related_name='keywords', to='sbirez.Topic'), 25 | preserve_default=True, 26 | ), 27 | migrations.AlterField( 28 | model_name='phase', 29 | name='topic', 30 | field=models.ForeignKey(related_name='phases', to='sbirez.Topic'), 31 | preserve_default=True, 32 | ), 33 | migrations.AlterField( 34 | model_name='reference', 35 | name='topic', 36 | field=models.ForeignKey(related_name='references', to='sbirez.Topic'), 37 | preserve_default=True, 38 | ), 39 | ] 40 | 41 | """ 42 | Manually removing this operation - I don't understand its purpose, and it causes 43 | 44 | 'CREATE INDEX "sbirez_topic_fts_64561dfd2a97ef59_uniq" ON "sbirez_topic" ("fts")' 45 | 46 | which PostgreSQL rejects with 47 | 48 | django.db.utils.OperationalError: index row requires 10552 bytes, maximum size is 8191 49 | 50 | and anyway we Do Not Want a unique index on this. 51 | 52 | Apparently one more case of django migrations not knowing how to handle a TSVECTOR column. 53 | 54 | migrations.AlterField( 55 | model_name='topic', 56 | name='fts', 57 | field=djorm_pgfulltext.fields.VectorField(null=True, default='', db_index=True, serialize=False, editable=False), 58 | preserve_default=True, 59 | ), 60 | """ 61 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/signin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('SignInCtrl', function ($scope, $rootScope, $window, $location, $state, UserService, AuthenticationService) { 5 | $rootScope.bodyClass = 'sign-in'; 6 | $scope.email = ''; 7 | $scope.password = ''; 8 | $scope.intention = $location.search(); 9 | if (!$scope.intention.target) { 10 | $scope.intention = null; 11 | } 12 | $scope.logIn = function logIn() { 13 | $scope.errorEmail = ''; 14 | $scope.errorPassword = ''; 15 | $scope.errorMsg = ''; 16 | if ($scope.email !== '' && $scope.password !== '') { 17 | UserService.logIn($scope.email, $scope.password).then(function(data) { 18 | $window.sessionStorage.token = data.data.token; 19 | $window.sessionStorage.username = data.data.username; 20 | $window.sessionStorage.userid = data.data.id; 21 | UserService.getUserDetails(data.data.id).then(function(data) { 22 | $window.sessionStorage.firmid = data.firm; 23 | $window.sessionStorage.expiration = data.password_expires; 24 | AuthenticationService.setAuthenticated(true); 25 | }); 26 | if ($scope.intention) { 27 | $location.path($scope.intention.target.replace(/%2F/g, '/')).search('target', null); 28 | } else { 29 | $state.go('app.proposals.list'); 30 | } 31 | },function(status) { 32 | if (status && status.data && status.data.non_field_errors) { 33 | $scope.errorMsg = status.data.non_field_errors[0]; 34 | if ($scope.errorMsg === 'Password has expired.') { 35 | $scope.errorMsg = 'Your password has expired. Please reset your password via the \'Forgot your password?\' link to continue.' 36 | } 37 | } else { 38 | if (status.data.email) { 39 | $scope.errorEmail = status.data.email[0]; 40 | } 41 | if (status.data.password) { 42 | $scope.errorPassword = status.data.password[0]; 43 | } 44 | $scope.errorMsg = 'Unable to log in.'; 45 | } 46 | }); 47 | } else { 48 | $scope.errorMsg = 'Please provide a username and password'; 49 | } 50 | }; 51 | }); 52 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/calculated.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('calculated', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | calculated: '=', 9 | multiplename: '=?', 10 | multipletoken: '=?' 11 | }, 12 | templateUrl: 'static/views/partials/elements/calculated.html', 13 | controller: ['$scope', 'ProposalService', 14 | function ($scope, ProposalService) { 15 | $scope.element = $scope.calculated; 16 | $scope.fieldName = $scope.element.human; 17 | $scope.options = []; 18 | $scope.visible = true; 19 | $scope.validationstorage = ''; 20 | 21 | var validationCallback = function(data) { 22 | $scope.storage = Math.round((data + 0.00001) * 100) / 100; 23 | $scope.apply(); 24 | }; 25 | 26 | var askIfCallback = function(data) { 27 | $scope.visible = (data === true || data === 'true'); 28 | }; 29 | 30 | $scope.apply = function() { 31 | ProposalService.apply($scope.element, $scope.storage, $scope.multipletoken); 32 | }; 33 | 34 | $scope.storage = ProposalService.register($scope.element, 35 | validationCallback, 36 | $scope.element.ask_if !== null ? askIfCallback : null, 37 | $scope.multipletoken); 38 | if ($scope.storage.length === undefined && typeof $scope.storage === 'object') { 39 | $scope.storage = ''; 40 | } else { 41 | $scope.storage = Math.round(($scope.storage + 0.00001) * 100) / 100; 42 | } 43 | 44 | $scope.fieldName = $scope.element.human; 45 | if ($scope.multiplename !== undefined && $scope.element.human.indexOf('%multiple%') > -1) { 46 | $scope.fieldName = $scope.element.human.replace('%multiple%', $scope.multiplename); 47 | } 48 | 49 | $scope.fieldToken = $scope.element.name; 50 | if ($scope.multipletoken !== undefined) { 51 | $scope.fieldToken = $scope.element.name + '_' + $scope.multipletoken; 52 | } 53 | 54 | if ($scope.multiplename !== undefined && $scope.fieldName.indexOf('%multiple%') > -1) { 55 | $scope.fieldName = $scope.fieldName.replace('%multiple%', $scope.multiplename); 56 | } 57 | } 58 | ] 59 | }; 60 | }); 61 | -------------------------------------------------------------------------------- /sbirez/migrations/0014_auto_20150319_1730.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import djorm_pgfulltext.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sbirez', '0013_auto_20150317_1907'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='proposal', 17 | name='data', 18 | field=models.TextField(null=True, blank=True), 19 | preserve_default=True, 20 | ), 21 | migrations.AlterField( 22 | model_name='question', 23 | name='ask_if', 24 | field=models.TextField(null=True, blank=True), 25 | preserve_default=True, 26 | ), 27 | migrations.AlterField( 28 | model_name='question', 29 | name='data_type', 30 | field=models.TextField(null=True, default='str'), 31 | preserve_default=True, 32 | ), 33 | migrations.AlterField( 34 | model_name='question', 35 | name='default', 36 | field=models.TextField(null=True, blank=True), 37 | preserve_default=True, 38 | ), 39 | migrations.AlterField( 40 | model_name='question', 41 | name='help', 42 | field=models.TextField(null=True, blank=True), 43 | preserve_default=True, 44 | ), 45 | migrations.AlterField( 46 | model_name='question', 47 | name='human', 48 | field=models.TextField(null=True, blank=True), 49 | preserve_default=True, 50 | ), 51 | migrations.AlterField( 52 | model_name='question', 53 | name='required', 54 | field=models.NullBooleanField(default=False), 55 | preserve_default=True, 56 | ), 57 | migrations.AlterField( 58 | model_name='question', 59 | name='validation', 60 | field=models.TextField(null=True, blank=True), 61 | preserve_default=True, 62 | ), 63 | migrations.AlterField( 64 | model_name='question', 65 | name='validation_msg', 66 | field=models.TextField(null=True, blank=True), 67 | preserve_default=True, 68 | ), 69 | migrations.AddField( 70 | model_name='proposal', 71 | name='title', 72 | field=models.TextField(default='Dummy Title'), 73 | preserve_default=False, 74 | ), 75 | ] 76 | -------------------------------------------------------------------------------- /sbirez/static/js/services/usersvc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').factory('UserService', function($http, $window, $rootScope, $location, $q, AuthenticationService) { 4 | var user = {}; 5 | return { 6 | logIn: function(username, password) { 7 | return $http.post('auth/', {email: username, password: password}); 8 | }, 9 | 10 | logOut: function() { 11 | $window.sessionStorage.token = ''; 12 | $window.sessionStorage.username = null; 13 | $window.sessionStorage.userid = null; 14 | $window.sessionStorage.firmid = null; 15 | $window.sessionStorage.expiration = null; 16 | user = {}; 17 | AuthenticationService.setAuthenticated(false); 18 | $location.path('/'); 19 | }, 20 | 21 | refreshToken: function() { 22 | return $http.post('auth-refresh/', {token: $window.sessionStorage.token}); 23 | }, 24 | 25 | resetPassword: function(email) { 26 | return $http.post('rest-auth/password/reset/', {'email': email}); 27 | }, 28 | 29 | changePassword: function(old_password, new_password1, new_password2) { 30 | user.name = null; 31 | return $http.post('rest-auth/password/change/', 32 | { 33 | 'old_password': old_password, 34 | 'new_password1': new_password1, 35 | 'new_password2': new_password2 36 | }); 37 | }, 38 | 39 | createUser: function(name, username, password) { 40 | return $http.post('api/v1/users/', 41 | { 42 | name: name, 43 | email: username, 44 | password: password, 45 | groups: [], 46 | is_staff: false 47 | }); 48 | }, 49 | 50 | getUserDetails: function(id) { 51 | var deferred = $q.defer(); 52 | if (user.name === null || user.name === undefined || user.name === '') { 53 | if (id === undefined || id === null) { 54 | id = $window.sessionStorage.userid; 55 | } 56 | $http.get('api/v1/users/' + id + '/').success(function(data) { 57 | user = data; 58 | $window.sessionStorage.expiration = data.password_expires; 59 | $rootScope.$broadcast('userUpdated', user); 60 | deferred.resolve(data); 61 | }).error(function(data) { 62 | deferred.reject(new Error(data)); 63 | }); 64 | } 65 | else { 66 | deferred.resolve(user); 67 | } 68 | return deferred.promise; 69 | }, 70 | 71 | updateUserDetails: function(id, user) { 72 | $http.put('api/v1/users/' + id + '/', user).success(function() { 73 | }); 74 | } 75 | }; 76 | }); 77 | -------------------------------------------------------------------------------- /tests/client/spec/controllers/savedopps.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: SavedOppsCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('sbirezApp')); 7 | 8 | var SavedOppsCtrl, 9 | scope, 10 | $state, 11 | AuthenticationService, 12 | mockDependency, 13 | $httpBackend, 14 | data, 15 | emptyData, 16 | propData; 17 | 18 | emptyData = { 19 | 'title': '', 20 | pre_release_date:'', 21 | proposals_begin_date:'', 22 | proposals_end_date:'' 23 | }; 24 | 25 | data = { 26 | 'results': [{ 27 | 'title': 'The title.', 28 | 'id':123, 29 | 'proposal':2, 30 | 'pre_release_date':'', 31 | 'proposals_begin_date':'', 32 | 'proposals_end_date':'' 33 | },{ 34 | 'title': 'The 2nd title.', 35 | 'id':456, 36 | 'pre_release_date':'', 37 | 'proposals_begin_date':'', 38 | 'proposals_end_date':'' 39 | }], 40 | 'count': 2 41 | }; 42 | 43 | propData = { 44 | 'results': [{ 45 | 'id': 2, 46 | 'topic': 123 47 | }] 48 | }; 49 | 50 | beforeEach(function(){ 51 | mockDependency = {}; 52 | mockDependency.params = {}; 53 | mockDependency.params.id = 1; 54 | 55 | inject(function (_$httpBackend_, $controller, $rootScope, _AuthenticationService_) { 56 | $httpBackend = _$httpBackend_; 57 | $httpBackend.whenGET('static/views/partials/main.html').respond({}); 58 | $httpBackend.whenGET('static/views/partials/search.html').respond({}); 59 | scope = $rootScope.$new(); 60 | $state = mockDependency; 61 | AuthenticationService = _AuthenticationService_; 62 | SavedOppsCtrl = $controller('SavedOppsCtrl', { 63 | $scope: scope, 64 | $state: mockDependency 65 | }); 66 | }); 67 | }); 68 | 69 | it('page should be empty before request is made', function () { 70 | expect(scope.data).toEqual({}); 71 | }); 72 | 73 | it('page should be populated when a request is made while logged in', function () { 74 | AuthenticationService.setAuthenticated(true); 75 | expect(scope.data).toEqual({}); 76 | $httpBackend.expectGET('api/v1/topics/?closed=true&saved=true').respond(data); 77 | $httpBackend.expectGET('api/v1/proposals/').respond(data); 78 | $httpBackend.flush(); 79 | expect(scope.data.results[0].proposal).toBe(2); 80 | }); 81 | 82 | it('page should not load if not logged in', function () { 83 | expect(scope.data).toEqual({}); 84 | $httpBackend.expectGET('api/v1/topics/?closed=true&saved=true').respond(404); 85 | $httpBackend.flush(); 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /tests/client/spec/controllers/accountOrganization.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AccountOrganizationCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('sbirezApp')); 7 | 8 | var AccountOrganizationCtrl, 9 | $rootScope, 10 | scope, 11 | $httpBackend, 12 | UserService, 13 | $q, 14 | data; 15 | 16 | data = {'id':1, 'name':'abc', 'naics':['111'], 'address':null, 'point_of_contact':null }; 17 | 18 | // Initialize the controller and a mock scope 19 | beforeEach(inject(function ($controller, _$rootScope_, _$httpBackend_, _UserService_, $q, $state) { 20 | $httpBackend = _$httpBackend_; 21 | $rootScope = _$rootScope_; 22 | UserService = _UserService_; 23 | scope = $rootScope.$new(); 24 | spyOn(UserService, 'getUserDetails').andCallFake(function() { 25 | var deferred = $q.defer(); 26 | deferred.resolve({'id':1, 'name':'Test User', 'firm':1}); 27 | return deferred.promise; 28 | }); 29 | 30 | $httpBackend.whenGET('static/views/partials/main.html').respond({}); 31 | $httpBackend.whenGET('static/views/partials/search.html').respond({}); 32 | $httpBackend.expectGET('api/v1/firms/1/') 33 | .respond(data); 34 | $httpBackend.whenGET('api/v1/naics/111').respond({'code': '111', 'description': 'Something about farming'}); 35 | 36 | //{"id":2,"name":"Dave Best","tax_id":null,"sbc_id":null,"duns_id":null,"cage_code":null,"website":null,"address":null,"point_of_contact":null,"founding_year":null,"naics":[],"phase1_count":null,"phase1_year":null,"phase2_count":null,"phase2_year":null,"phase2_employees":null,"current_employees":null,"patent_count":null,"total_revenue_range":null,"revenue_percent":null} 37 | 38 | AccountOrganizationCtrl = $controller('AccountOrganizationCtrl', { 39 | $scope: scope, 40 | $state: {params: {'id':1}, go: function() {}} 41 | }); 42 | })); 43 | 44 | it('should call the api to retrieve organization details', function() { 45 | $httpBackend.flush(); 46 | expect(scope.firm).toBeDefined(); 47 | }); 48 | 49 | it('should post data to the server if a firm is not defined on submit', function() { 50 | $httpBackend.flush(); 51 | scope.orgId = null; 52 | scope.updateFirm(); 53 | $httpBackend.expectPOST('api/v1/firms/').respond(data); 54 | $httpBackend.flush(); 55 | }); 56 | 57 | it('should put data to the server if a firm is defined on submit', function() { 58 | $httpBackend.flush(); 59 | expect(scope.orgId).toBeDefined(); 60 | scope.updateFirm(); 61 | $httpBackend.expectPATCH('api/v1/firms/1/').respond(data); 62 | $httpBackend.flush(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/singlelineitem.html: -------------------------------------------------------------------------------- 1 |
2 | {{::element.human}} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/topicDetails.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{errorMsg}}

5 |

Search for topics from our Home page. 6 |

7 | Back to search results 8 |
9 |

{{data.title}}

10 |

{{data.program}} {{data.topic_number}}

11 | 17 |
18 |
19 |

Objective

20 |

{{data.objective}}

21 |
22 |
23 |

Research & Technical Areas

24 |

{{area.area}}

25 |
26 |
27 |

Description

28 |

{{data.description}}

29 |

{{phaseObj.phase}}

30 |
31 |
32 |

References

33 |
    34 |
  1. {{referenceObj.reference}}
  2. 35 |
36 |
37 |
38 |

Keywords

39 |

{{getKeywordList()}}

40 |
41 |
42 |

Technical Points of Contact

43 |
44 | 45 |
Office: {{poc.office}}
46 |
{{poc.email}}
47 |
{{poc.phone}}
48 |
Fax: {{poc.fax}}
49 |
50 |
51 |
52 |
Solicitation ID:
53 |
{{data.solicitation.name}}
54 |
Pre-Release Date:
55 |
{{data.solicitation.pre_release_date | date}}
56 |
Proposal Submission Begin Date:
57 |
{{data.solicitation.proposals_begin_date | date }}
58 |
Proposal Submission End Date:
59 |
{{data.solicitation.proposals_end_date | date }}
60 |
61 |
62 | 63 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['jasmine'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'sbirez/static/lib/jquery/dist/jquery.min.js', 15 | 'sbirez/static/lib/angular/angular.js', 16 | 'sbirez/static/lib/angular-mocks/angular-mocks.js', 17 | 'sbirez/static/lib/angular-resource/angular-resource.js', 18 | 'sbirez/static/lib/angular-aria/angular-aria.js', 19 | 'sbirez/static/lib/angular-cookies/angular-cookies.js', 20 | 'sbirez/static/lib/angular-sanitize/angular-sanitize.js', 21 | 'sbirez/static/lib/angular-route/angular-route.js', 22 | 'sbirez/static/lib/ng-file-upload/angular-file-upload.js', 23 | 'sbirez/static/lib/ngDialog/js/ngDialog.min.js', 24 | 'sbirez/static/lib/angular-ui-router/release/angular-ui-router.js', 25 | 'sbirez/static/lib/angular-order-object-by/src/ng-order-object-by.js', 26 | 'sbirez/static/jslib/parser/parser.js', 27 | 'sbirez/static/js/app.js', 28 | 'sbirez/static/js/controllers/*.js', 29 | 'sbirez/static/js/services/*.js', 30 | 'sbirez/static/js/directives/*.js', 31 | 'sbirez/static/js/filters/*.js', 32 | 'tests/client/spec/**/*.js', 33 | 34 | 'sbirez/static/views/partials/workflow.html', 35 | 'sbirez/static/views/partials/elements/*.html' 36 | ], 37 | 38 | ngHtml2JsPreprocessor: { 39 | // strip this from the file path 40 | stripPrefix: 'sbirez/', 41 | }, 42 | preprocessors : { 43 | 'sbirez/static/views/partials/workflow.html': 'ng-html2js', 44 | 'sbirez/static/views/partials/elements/*.html': 'ng-html2js' 45 | }, 46 | 47 | // list of files / patterns to exclude 48 | exclude: ['sbirez/static/js/vendor/*.js'], 49 | 50 | // web server port 51 | port: 8080, 52 | 53 | // level of logging 54 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 55 | logLevel: config.LOG_INFO, 56 | 57 | 58 | // enable / disable watching file and executing tests whenever any file changes 59 | autoWatch: false, 60 | 61 | 62 | // Start these browsers, currently available: 63 | // - Chrome 64 | // - ChromeCanary 65 | // - Firefox 66 | // - Opera 67 | // - Safari (only Mac) 68 | // - PhantomJS 69 | // - IE (only Windows) 70 | browsers: ['Firefox'], 71 | 72 | 73 | // Continuous Integration mode 74 | // if true, it capture browsers, run tests and exit 75 | singleRun: true 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/lineitem.html: -------------------------------------------------------------------------------- 1 |
2 | {{::element.human}} 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
-------------------------------------------------------------------------------- /sbirez/static/views/partials/elements/dynamiclineitem.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{::element.human}}

4 | Add another 5 |
6 |
7 | {{::element.human}} {{multiple.value + 1}} 8 | Remove 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/str.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('str', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | str: '=', 9 | multiplename: '=?', 10 | multipletoken: '=?' 11 | }, 12 | templateUrl: 'static/views/partials/elements/str.html', 13 | controller: ['$scope', 'ProposalService', 14 | function ($scope, ProposalService) { 15 | $scope.element = $scope.str; 16 | $scope.fieldName = $scope.element.human; 17 | $scope.options = []; 18 | $scope.visible = true; 19 | $scope.validationstorage = ''; 20 | 21 | var validationCallback = function(data) { 22 | $scope.validationstorage = data; 23 | }; 24 | 25 | var askIfCallback = function(data) { 26 | $scope.visible = (data === true || data === 'true'); 27 | }; 28 | 29 | $scope.storage = ProposalService.register($scope.element, 30 | validationCallback, 31 | $scope.element.ask_if !== null ? askIfCallback : null, 32 | $scope.multipletoken); 33 | if ($scope.storage.length === undefined) { 34 | $scope.storage = ''; 35 | } 36 | 37 | $scope.fieldName = $scope.element.human; 38 | if ($scope.multiplename !== undefined && $scope.element.human.indexOf('%multiple%') > -1) { 39 | $scope.fieldName = $scope.element.human.replace('%multiple%', $scope.multiplename); 40 | } 41 | 42 | $scope.fieldToken = $scope.element.name; 43 | if ($scope.multipletoken !== undefined) { 44 | $scope.fieldToken = $scope.element.name + '_' + $scope.multipletoken; 45 | } 46 | 47 | $scope.apply = function() { 48 | ProposalService.apply($scope.element, $scope.storage, $scope.multipletoken); 49 | }; 50 | 51 | if ($scope.multiplename !== undefined && $scope.fieldName.indexOf('%multiple%') > -1) { 52 | $scope.fieldName = $scope.fieldName.replace('%multiple%', $scope.multiplename); 53 | } 54 | 55 | if ($scope.element.validation) { 56 | var validationElements = $scope.element.validation.split(' '); 57 | if (validationElements[0] === 'one_of' && validationElements.length > 2) { 58 | validationElements.splice(0,1); 59 | if (validationElements[0].slice(0, 1) === '"') { 60 | var options = validationElements.join(' '); 61 | validationElements = options.split(','); 62 | for (var i = 0; i < validationElements.length; i++) { 63 | validationElements[i] = validationElements[i].trim(); 64 | validationElements[i] = validationElements[i].slice(1, validationElements[i].length - 1); 65 | } 66 | $scope.options = validationElements; 67 | } else { 68 | $scope.options = validationElements; 69 | } 70 | } 71 | } 72 | } 73 | ] 74 | }; 75 | }); 76 | -------------------------------------------------------------------------------- /tests/client/spec/services/usersvc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: UserService', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('sbirezApp')); 7 | 8 | var $window, 9 | UserService, 10 | $httpBackend, 11 | AuthenticationService; 12 | 13 | // Initialize the controller and a mock scope 14 | beforeEach(inject(function (_$httpBackend_, _UserService_, _$window_, _AuthenticationService_) { 15 | $httpBackend = _$httpBackend_; 16 | $window = _$window_; 17 | AuthenticationService = _AuthenticationService_; 18 | $httpBackend.whenGET('static/views/partials/main.html').respond({}); 19 | UserService = _UserService_; 20 | })); 21 | 22 | afterEach(function() { 23 | $httpBackend.verifyNoOutstandingExpectation(); 24 | $httpBackend.verifyNoOutstandingRequest(); 25 | }); 26 | 27 | // logIn 28 | it('should post to the backend with the expected user name and password', function() { 29 | UserService.logIn('user', 'password'); 30 | $httpBackend.expect('POST', 'auth/', {email: 'user', password: 'password'}).respond(200); 31 | $httpBackend.flush(); 32 | }); 33 | 34 | // logOut 35 | it('should clear session storage, set to unauthenticated, and redirect to root', function() { 36 | $window.sessionStorage.token = 'abc'; 37 | $window.sessionStorage.username = 'Test'; 38 | $window.sessionStorage.userid = 1; 39 | UserService.logOut('user', 'password'); 40 | expect($window.sessionStorage.token).toBe(''); 41 | expect($window.sessionStorage.username).toBe('null'); 42 | expect($window.sessionStorage.userid).toBe('null'); 43 | expect(AuthenticationService.getAuthenticated()).toBe(false); 44 | 45 | $httpBackend.flush(); 46 | }); 47 | 48 | // refreshToken 49 | it('should post to the backend with the existing token', function() { 50 | $window.sessionStorage.token = 'abc'; 51 | UserService.refreshToken(); 52 | $httpBackend.expect('POST', 'auth-refresh/', {token: 'abc'}).respond(200); 53 | $httpBackend.flush(); 54 | }); 55 | 56 | // resetPassword 57 | it ('should post to the password reset endpoint', function() { 58 | UserService.resetPassword('a@b.com'); 59 | $httpBackend.expect('POST', 'rest-auth/password/reset/', {'email': 'a@b.com'}).respond(200); 60 | $httpBackend.flush(); 61 | }); 62 | 63 | // createUser 64 | it ('should post to the create user endpoint with correctly formatted parameters', function() { 65 | $window.sessionStorage.token = ''; 66 | UserService.createUser('test', 'a@b.com', 'abc'); 67 | $httpBackend.expect('POST', 'api/v1/users/', 68 | {'name': 'test', 69 | 'email': 'a@b.com', 70 | 'password': 'abc', 71 | 'groups': [], 72 | 'is_staff': false }).respond(200); 73 | $httpBackend.flush(); 74 | }); 75 | 76 | // changePassword 77 | it ('should post to the change password endpoint with correct parameters', function() { 78 | AuthenticationService.setAuthenticated(true); 79 | UserService.changePassword('oldpass', 'newpass', 'newpass'); 80 | $httpBackend.expect('POST', 'rest-auth/password/change/', 81 | { 82 | 'old_password': 'oldpass', 83 | 'new_password1': 'newpass', 84 | 'new_password2': 'newpass' 85 | }).respond(200); 86 | $httpBackend.flush(); 87 | }) 88 | }); 89 | -------------------------------------------------------------------------------- /sbirez/static/js/controllers/accountOrganization.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp') 4 | .controller('AccountOrganizationCtrl', function ($scope, $http, $state, $q, $window, $rootScope, UserService) { 5 | 6 | $scope.orgId = null; 7 | $scope.firm = {}; 8 | $scope.firm.point_of_contact = {}; 9 | $scope.firm.address = {}; 10 | $scope.validationData = {}; 11 | $scope.errorState = false; 12 | $scope.naicsdata = []; 13 | $rootScope.bodyClass = 'company'; 14 | 15 | UserService.getUserDetails().then(function(data) { 16 | $scope.orgId = data.firm; 17 | if ($scope.orgId !== null && $scope.orgId !== undefined) { 18 | $http.get('api/v1/firms/' + $scope.orgId + '/').success(function(data) { 19 | $scope.firm = data; 20 | for (var i = 0; i < 4; i++) { 21 | $scope.applyNaics(i); 22 | } 23 | }); 24 | } 25 | }, function(error) { 26 | console.log(error); 27 | }); 28 | 29 | $scope.applyNaics = function(index) { 30 | $scope.naicsdata[index] = {}; 31 | $scope.naicsdata[index].error = false; 32 | if ($scope.firm.naics && $scope.firm.naics[index] !== undefined && $scope.firm.naics[index].length > 2) { 33 | $http.get('api/v1/naics/' + $scope.firm.naics[index]).success(function(data) { 34 | $scope.naicsdata[index].detail = data.description; 35 | }).error(function(data) { 36 | $scope.naicsdata[index].detail = data.detail; 37 | $scope.naicsdata[index].error = true; 38 | }); 39 | } else { 40 | $scope.naicsdata[index].detail = ''; 41 | } 42 | }; 43 | 44 | var consolidateNaics = function() { 45 | if ($scope.firm.naics) { 46 | for (var i = $scope.firm.naics.length - 1; i >= 0; i--) { 47 | if ($scope.firm.naics[i] === undefined || $scope.firm.naics[i] === null || $scope.firm.naics[i] === '') { 48 | $scope.firm.naics.splice(i, 1); 49 | } 50 | } 51 | } 52 | }; 53 | 54 | $scope.updateFirm = function() { 55 | if ($scope.orgId === null || $scope.orgId === undefined) { 56 | consolidateNaics(); 57 | $http.post('api/v1/firms/', $scope.firm).success(function(data) { 58 | $scope.firm = data; 59 | $scope.orgId = data.id; 60 | $window.sessionStorage.firmid = data.id; 61 | }).error(function(data,status) { 62 | console.log('update', data, status); 63 | $scope.errorState = true; 64 | $scope.validationData = data; 65 | }); 66 | } else { 67 | if ($scope.firm.address === null) { 68 | delete $scope.firm.address; 69 | } 70 | if ($scope.firm.point_of_contact === null) { 71 | delete $scope.firm.point_of_contact; 72 | } 73 | consolidateNaics(); 74 | 75 | $http.patch('api/v1/firms/' + $scope.orgId + '/', $scope.firm).success(function(data) { 76 | $scope.firm = data; 77 | $state.go('app.proposals.list'); 78 | }).error(function(data, status) { 79 | $scope.errorState = true; 80 | $scope.validationData = data; 81 | for (var i = 0; i < 4; i++) { 82 | $scope.applyNaics(i); 83 | } 84 | }); 85 | } 86 | }; 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /.about.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # .about.yml project metadata 3 | # 4 | # Short name that acts as the project identifier (required) 5 | name: SBIR-EZ 6 | 7 | # Full proper name of the project (required) 8 | full_name: SBIR-EZ 9 | 10 | # The type of content in the repo 11 | # values: app, docs, policy 12 | type: app 13 | 14 | # Describes whether a project team, working group/guild, etc. owns the repo (required) 15 | # values: guild, working-group, project 16 | owner_type: project 17 | 18 | # Name of the main project repo if this is a sub-repo; name of the working group/guild repo if this is a working group/guild subproject 19 | # parent: 20 | 21 | # Maturity stage of the project (required) 22 | # values: discovery, alpha, beta, live 23 | stage: alpha 24 | 25 | # Whether or not the project is actively maintained (required) 26 | # values: active, deprecated 27 | status: active 28 | 29 | # Description of the project 30 | description: > 31 | SBIR-EZ (sih-bur-easy) attempts to simplify and streamline the submission process for 32 | proposals to the Air Force's Small Business Innovation Research Program. 33 | 34 | # Should be 'true' if the project has a continuous build (required) 35 | # values: true, false 36 | testable: true 37 | 38 | # Team members contributing to the project (required) 39 | # Items: 40 | # - github: GitHub user name 41 | # id: Internal team identifier/user name 42 | # role: Team member's role; leads should be designated as 'lead' 43 | team: 44 | - github: DavidEBest 45 | id: David Best 46 | role: lead 47 | - github: catherinedevlin 48 | id: Catherine Devlin 49 | role: backend development 50 | - github: andrewmaier 51 | id: Andrew Maier 52 | role: design 53 | 54 | 55 | # Partners for whom the project is developed 56 | partners: 57 | - Air Force 58 | 59 | # Brief descriptions of significant project developments 60 | milestones: 61 | - May 2014: 18F began work on project 62 | 63 | # Technologies used to build the project 64 | stack: 65 | - Python 66 | - Django 67 | - django-rest-framework 68 | - JavaScript 69 | - angular 70 | 71 | # Brief description of the project's outcomes 72 | impact: To encourage new small businesses to work with the Federal Government. 73 | 74 | # Services used to supply project status information 75 | # Items: 76 | # - name: Name of the service 77 | # category: Type of the service 78 | # url: URL for detailed information 79 | # badge: URL for the status badge 80 | services: 81 | - name: Travis 82 | category: Continuous Integration 83 | url: https://travis-ci.org/18F/afsbirez 84 | badge: https://travis-ci.org/18F/afsbirez.svg?branch=master 85 | 86 | # Licenses that apply to the project and/or its components (required) 87 | # Items by property name pattern: 88 | # .*: 89 | # name: Name of the license from the Software Package Data Exchange (SPDX): https://spdx.org/licenses/ 90 | # url: URL for the text of the license 91 | licenses: 92 | sbir-ez: 93 | name: CC0 94 | url: https://github.com/18F/afsbirez/blob/master/LICENSE.md 95 | 96 | # Blogs or websites associated with project development 97 | # blog: 98 | # - 99 | 100 | # Links to project artifacts 101 | # Items: 102 | # - url: URL for the link 103 | # text: Anchor text for the link 104 | # links: TODO 105 | # - 106 | 107 | # Email addresses of points-of-contact 108 | contact: 109 | - url: mailto:david.best@gsa.gov 110 | text: David Best 111 | -------------------------------------------------------------------------------- /sbirez/assets.py: -------------------------------------------------------------------------------- 1 | from django_assets import Bundle, register 2 | 3 | bower_js = Bundle( 4 | "jslib/parser/parser.js", 5 | "lib/jquery/dist/jquery.min.js", 6 | "lib/angular/angular.js", 7 | "lib/angular-resource/angular-resource.js", 8 | "lib/angular-cookies/angular-cookies.js", 9 | "lib/angular-sanitize/angular-sanitize.js", 10 | "lib/angular-aria/angular-aria.js", 11 | "lib/angular-route/angular-route.js", 12 | "lib/ng-file-upload/angular-file-upload.js", 13 | "lib/ngDialog/js/ngDialog.min.js", 14 | "lib/angular-ui-router/release/angular-ui-router.js", 15 | "lib/angular-order-object-by/src/ng-order-object-by.js", 16 | "lib/base64/base64.min.js", 17 | "lib/bigfoot/dist/bigfoot.min.js", 18 | filters="jsmin", 19 | output="js/bower.min.%(version)s.js" 20 | ) 21 | 22 | angular_js = Bundle( 23 | "js/app.js", 24 | "js/filters/truncate.js", 25 | "js/directives/workflow.js", 26 | "js/directives/topic.js", 27 | "js/directives/pagination.js", 28 | "js/directives/backbutton.js", 29 | "js/directives/jargon.js", 30 | "js/directives/header.js", 31 | "js/directives/footer.js", 32 | "js/directives/elements/str.js", 33 | "js/directives/elements/text.js", 34 | "js/directives/elements/bool.js", 35 | "js/directives/elements/checkbox.js", 36 | "js/directives/elements/group.js", 37 | "js/directives/elements/lineitem.js", 38 | "js/directives/elements/singlelineitem.js", 39 | "js/directives/elements/dynamiclineitem.js", 40 | "js/directives/elements/readonlytext.js", 41 | "js/directives/elements/upload.js", 42 | "js/directives/elements/calculated.js", 43 | "js/controllers/main.js", 44 | "js/controllers/search.js", 45 | "js/controllers/signin.js", 46 | "js/controllers/signup.js", 47 | "js/controllers/landing.js", 48 | "js/controllers/reset.js", 49 | "js/controllers/form.js", 50 | "js/controllers/contact.js", 51 | "js/controllers/accountUser.js", 52 | "js/controllers/accountOrganization.js", 53 | "js/controllers/savedOpps.js", 54 | "js/controllers/history.js", 55 | "js/controllers/notification.js", 56 | "js/controllers/documentList.js", 57 | "js/controllers/document.js", 58 | "js/controllers/proposal.js", 59 | "js/controllers/proposalReport.js", 60 | "js/controllers/adminuser.js", 61 | "js/controllers/topic.js", 62 | "js/services/authsvc.js", 63 | "js/services/dialogsvc.js", 64 | "js/services/usersvc.js", 65 | "js/services/oppsvc.js", 66 | "js/services/searchsvc.js", 67 | "js/services/tokensvc.js", 68 | "js/services/proposalsvc.js", 69 | "js/services/documentsvc.js", 70 | "js/services/validationsvc.js", 71 | filters="jsmin", 72 | output="js/angular.min.%(version)s.js" 73 | ) 74 | 75 | combined_sass = Bundle( 76 | "sass/main.scss", 77 | filters="pyscss", 78 | output="css/main.min.css" 79 | ) 80 | 81 | combined_css = Bundle( 82 | "css/reset.css", 83 | "lib/ngDialog/css/ngDialog.css", 84 | "lib/ngDialog/css/ngDialog-theme-default.css", 85 | "css/ngdialog-theme-login.css", 86 | "css/ngdialog-theme-intromessage.css", 87 | "css/ngdialog-theme-logout.css", 88 | "lib/bigfoot/dist/bigfoot-default.css", 89 | combined_sass, 90 | filters="cssmin", 91 | output="css/sbirez.min.%(version)s.css" 92 | ) 93 | 94 | register("bower_js", bower_js) 95 | register("angular_js", angular_js) 96 | register("combined_css", combined_css) 97 | -------------------------------------------------------------------------------- /sbirez/static/js/directives/elements/upload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('sbirezApp').directive('upload', function() { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | scope: { 8 | upload: '=', 9 | proposal: '@', 10 | multiplename: '=?', 11 | multipletoken: '=?' 12 | }, 13 | templateUrl: 'static/views/partials/elements/upload.html', 14 | controller: ['$scope', 'DocumentService', 'ProposalService', 15 | function ($scope, DocumentService, ProposalService) { 16 | $scope.element = $scope.upload; 17 | var fileId = null; 18 | $scope.validationstorage = ''; 19 | $scope.visible = true; 20 | 21 | var validationCallback = function(data) { 22 | $scope.validationstorage = data; 23 | }; 24 | 25 | var askIfCallback = function(data) { 26 | $scope.visible = (data === true || data === 'true'); 27 | }; 28 | 29 | $scope.storage = ProposalService.register($scope.element, 30 | validationCallback, 31 | $scope.element.ask_if !== null ? askIfCallback : null, 32 | $scope.multipletoken); 33 | if (typeof $scope.storage === 'object') { 34 | $scope.storage = undefined; 35 | } 36 | 37 | $scope.fieldName = $scope.element.human; 38 | if ($scope.multiplename !== undefined && $scope.element.human.indexOf('%multiple%') > -1) { 39 | $scope.fieldName = $scope.element.human.replace('%multiple%', $scope.multiplename); 40 | } 41 | 42 | $scope.fieldToken = $scope.element.name; 43 | if ($scope.multipletoken !== undefined) { 44 | $scope.fieldToken = $scope.element.name + '_' + $scope.multipletoken; 45 | } 46 | 47 | $scope.apply = function() { 48 | ProposalService.apply($scope.element, $scope.storage, $scope.multipletoken); 49 | }; 50 | 51 | if ($scope.storage !== undefined) { 52 | fileId = parseInt($scope.storage); 53 | if (fileId) { 54 | DocumentService.get(fileId).then(function(data) { 55 | $scope.selectedFiles = []; 56 | $scope.selectedFiles[0] = data; 57 | $scope.selectedFiles[0].filename = data.name; 58 | }, function() { 59 | $scope.fileId = null; 60 | $scope.selectedFiles = []; 61 | $scope.storage = null; 62 | }); 63 | } 64 | } 65 | 66 | $scope.onFileSelect = function($files) { 67 | $scope.selectedFiles = $files; 68 | $scope.progress = []; 69 | 70 | for (var j = 0; j < $scope.selectedFiles.length; j++) { 71 | $scope.selectedFiles[j].filename = $scope.selectedFiles[j].name; 72 | $scope.progress[j] = -1; 73 | } 74 | }; 75 | 76 | $scope.start = function(index) { 77 | DocumentService.upload($scope.selectedFiles[index], 78 | $scope.selectedFiles[index].filename, 79 | $scope.selectedFiles[index].filename, 80 | $scope.proposal, 81 | fileId, 82 | function(val) { 83 | console.log('progress', val); 84 | $scope.progress[index] = val; 85 | }).then(function(data) { 86 | $scope.storage = data.id; 87 | $scope.apply(); 88 | }); 89 | }; 90 | } 91 | ] 92 | }; 93 | }); 94 | -------------------------------------------------------------------------------- /afsbirez/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.conf import settings 3 | from django.contrib import admin 4 | from django.views.generic import TemplateView 5 | 6 | from rest_framework import routers 7 | from rest_framework_proxy.views import ProxyView 8 | from sbirez import api, models, views 9 | 10 | router = routers.DefaultRouter() 11 | router.register(r'users', api.UserViewSet) 12 | router.register(r'naics', api.NaicsViewSet) 13 | router.register(r'firms', api.FirmViewSet) 14 | router.register(r'groups', api.GroupViewSet) 15 | router.register(r'topics', api.TopicViewSet, 'topics') 16 | router.register(r'proposals/partial', api.PartialProposalViewSet) 17 | router.register(r'proposals', api.ProposalViewSet) 18 | router.register(r'proposal', api.ProposalViewSet) 19 | router.register(r'addresses', api.AddressViewSet) 20 | router.register(r'persons', api.PersonViewSet) 21 | router.register(r'documents', api.DocumentViewSet) 22 | router.register(r'documentversions', api.DocumentVersionViewSet) 23 | router.register(r'elements', api.ElementViewSet) 24 | router.register(r'jargons', api.JargonViewSet) 25 | 26 | urlpatterns = patterns('', 27 | url(r'^api/v1/topics/(?P[0-9]+)/saved/$', api.SaveTopicView.as_view()), 28 | url(r'^api/v1/proposals/(?P[0-9]+)/partial/$', api.PartialProposalViewSet.as_view( 29 | actions={'patch': 'partial_update', 'put': 'update', 'delete': 'destroy', } 30 | )), 31 | 32 | # api navigation 33 | url(r'^api/v1/', include(router.urls)), 34 | 35 | #django default admin 36 | url(r'^admin/', include(admin.site.urls)), 37 | 38 | # endpoints for password reset workflow. 39 | url(r'^rest-auth/', include('rest_auth.urls')), 40 | url(r'^password-reset/$', 41 | TemplateView.as_view(template_name="password_reset.html"), 42 | name='password-reset'), 43 | url(r'^password-reset/confirm/$', 44 | TemplateView.as_view(template_name="password_reset_confirm.html"), 45 | name='password-reset-confirm'), 46 | url(r'^password-reset/confirm/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 47 | TemplateView.as_view(template_name="password_reset_confirm.html"), 48 | name='password_reset_confirm'), 49 | 50 | # permit downloads of uploaded files 51 | url(r'^api/v1/documents/(?P[0-9]+)/file/$', 52 | api.FileDownloadView.as_view(model=models.Document, file_field='file')), 53 | url(r'^api/v1/documentversions/(?P[0-9]+)/file/$', 54 | api.FileDownloadView.as_view(model=models.DocumentVersion, file_field='file')), 55 | 56 | # jwt authentication endpoint 57 | url(r'^auth/', 'rest_framework_jwt.views.obtain_jwt_token'), 58 | url(r'^auth-refresh/', 'rest_framework_jwt.views.refresh_jwt_token'), 59 | 60 | # angular app endpoint 61 | url(r'^$', 'sbirez.views.home', name='home'), 62 | url(r'^search/', 'sbirez.views.home', name='home'), 63 | url(r'^topic/', 'sbirez.views.home', name='home'), 64 | url(r'^~/', 'sbirez.views.home', name='home'), 65 | url(r'^signin', 'sbirez.views.home', name='home'), 66 | url(r'^signup', 'sbirez.views.home', name='home'), 67 | url(r'^reset/', 'sbirez.views.home', name='home'), 68 | 69 | # proxy company info searches to SAM API 70 | url(r'^api/v1/firms/search/(?P.*)$', 71 | ProxyView.as_view(source='registrations?qterms=%(searchterms)s&api_key=' 72 | + settings.REST_PROXY['API_KEY']), 73 | name='firm-search'), 74 | ) 75 | --------------------------------------------------------------------------------