├── .cfignore ├── acqstackdb ├── __init__.py ├── tests │ └── test_tests.py ├── wsgi.py ├── urls.py └── settings.py ├── .python-version ├── acquisitions ├── __init__.py ├── tests │ ├── test_forms.py │ ├── test_views.py │ ├── test_add_teammate.py │ ├── test_team.py │ └── test_models.py ├── migrations │ ├── __init__.py │ ├── 0010_merge.py │ ├── 0002_auto_20160527_2144.py │ ├── 0020_auto_20160712_2020.py │ ├── 0015_auto_20160630_0358.py │ ├── 0018_stage_wip_limit.py │ ├── 0016_steptrackthroughmodel_wip_limit.py │ ├── 0005_auto_20160607_1618.py │ ├── 0017_auto_20160711_1544.py │ ├── 0014_auto_20160628_2334.py │ ├── 0009_auto_20160624_1622.py │ ├── 0012_auto_20160628_2017.py │ ├── 0004_auto_20160606_1921.py │ ├── 0006_auto_20160609_1416.py │ ├── 0008_role_squashed_0010_auto_20160622_0918.py │ ├── 0013_auto_20160628_2333.py │ ├── 0021_auto_20160712_2135.py │ ├── 0019_auto_20160712_1441.py │ ├── 0008_auto_20160623_1638.py │ ├── 0011_auto_20160628_2013.py │ ├── 0022_auto_20160726_2026.py │ ├── 0001_initial.py │ ├── 0003_auto_20160531_1702_squashed_0007_auto_20160531_2006.py │ └── 0007_auto_20160617_0357.py ├── apps.py ├── fixtures │ ├── tracks.json │ ├── actors.json │ ├── stages.json │ ├── steps.json │ └── steptrack.json ├── templates │ └── acquisitions │ │ ├── new_index.html │ │ ├── stages.html │ │ ├── new.html │ │ ├── includes │ │ └── item.html │ │ ├── layout.html │ │ ├── index.html │ │ └── acquisition.html ├── static │ └── acquisitions │ │ └── css │ │ └── style.css ├── management │ └── commands │ │ ├── add_teammate.py │ │ └── seed_database.py ├── providers │ └── fake_agency.py ├── admin.py ├── forms.py ├── factories.py ├── views.py └── models.py ├── runtime.txt ├── setup.cfg ├── pytest.ini ├── Procfile ├── third-party ├── uswds │ ├── img │ │ ├── plus.png │ │ ├── minus.png │ │ ├── search.png │ │ ├── correct8.png │ │ ├── correct9.png │ │ ├── logo-img.png │ │ ├── alerts │ │ │ ├── info.png │ │ │ ├── error.png │ │ │ ├── success.png │ │ │ ├── warning.png │ │ │ ├── success.svg │ │ │ ├── warning.svg │ │ │ ├── error.svg │ │ │ └── info.svg │ │ ├── arrow-down.png │ │ ├── arrow-right.png │ │ ├── us_flag_small.png │ │ ├── favicons │ │ │ ├── favicon.ico │ │ │ ├── favicon.png │ │ │ ├── favicon-16.png │ │ │ ├── favicon-57.png │ │ │ ├── favicon-72.png │ │ │ ├── favicon-114.png │ │ │ ├── favicon-144.png │ │ │ └── favicon-192.png │ │ ├── social-icons │ │ │ ├── png │ │ │ │ ├── rss25.png │ │ │ │ ├── twitter16.png │ │ │ │ ├── youtube15.png │ │ │ │ └── facebook25.png │ │ │ └── svg │ │ │ │ ├── facebook25.svg │ │ │ │ ├── twitter16.svg │ │ │ │ ├── rss25.svg │ │ │ │ └── youtube15.svg │ │ ├── arrow-down.svg │ │ ├── minus.svg │ │ ├── correct8.svg │ │ ├── correct9.svg │ │ ├── arrow-right.svg │ │ ├── plus.svg │ │ └── search.svg │ └── fonts │ │ ├── merriweather-bold-webfont.eot │ │ ├── merriweather-bold-webfont.ttf │ │ ├── merriweather-bold-webfont.woff │ │ ├── merriweather-bold-webfont.woff2 │ │ ├── merriweather-italic-webfont.eot │ │ ├── merriweather-italic-webfont.ttf │ │ ├── merriweather-light-webfont.eot │ │ ├── merriweather-light-webfont.ttf │ │ ├── merriweather-light-webfont.woff │ │ ├── sourcesanspro-bold-webfont.eot │ │ ├── sourcesanspro-bold-webfont.ttf │ │ ├── sourcesanspro-bold-webfont.woff │ │ ├── sourcesanspro-light-webfont.eot │ │ ├── sourcesanspro-light-webfont.ttf │ │ ├── merriweather-italic-webfont.woff │ │ ├── merriweather-italic-webfont.woff2 │ │ ├── merriweather-light-webfont.woff2 │ │ ├── merriweather-regular-webfont.eot │ │ ├── merriweather-regular-webfont.ttf │ │ ├── merriweather-regular-webfont.woff │ │ ├── sourcesanspro-bold-webfont.woff2 │ │ ├── sourcesanspro-italic-webfont.eot │ │ ├── sourcesanspro-italic-webfont.ttf │ │ ├── sourcesanspro-italic-webfont.woff │ │ ├── sourcesanspro-light-webfont.woff │ │ ├── sourcesanspro-light-webfont.woff2 │ │ ├── sourcesanspro-regular-webfont.eot │ │ ├── sourcesanspro-regular-webfont.ttf │ │ ├── merriweather-regular-webfont.woff2 │ │ ├── sourcesanspro-italic-webfont.woff2 │ │ ├── sourcesanspro-regular-webfont.woff │ │ └── sourcesanspro-regular-webfont.woff2 └── wait-for-it │ ├── LICENSE │ ├── README.md │ └── wait-for-it.sh ├── Dockerfile ├── .env.example ├── scripts └── cf-db.sh ├── manage.py ├── run.sh ├── .codeclimate.yml ├── requirements.txt ├── docker-compose.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── .jshintrc ├── .gitignore ├── README.md ├── .travis.yml ├── documentation └── setup.md └── .about.yml /.cfignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /acqstackdb/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.5.1 2 | -------------------------------------------------------------------------------- /acquisitions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.5.1 2 | -------------------------------------------------------------------------------- /acquisitions/tests/test_forms.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /acquisitions/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pep8] 2 | max-line-length = 160 3 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE=acqstackdb.settings 3 | -------------------------------------------------------------------------------- /acqstackdb/tests/test_tests.py: -------------------------------------------------------------------------------- 1 | def test_tests(): 2 | assert 1 == 1 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: python manage.py migrate && python manage.py runserver 0.0.0.0:$PORT 2 | -------------------------------------------------------------------------------- /third-party/uswds/img/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/plus.png -------------------------------------------------------------------------------- /third-party/uswds/img/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/minus.png -------------------------------------------------------------------------------- /third-party/uswds/img/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/search.png -------------------------------------------------------------------------------- /third-party/uswds/img/correct8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/correct8.png -------------------------------------------------------------------------------- /third-party/uswds/img/correct9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/correct9.png -------------------------------------------------------------------------------- /third-party/uswds/img/logo-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/logo-img.png -------------------------------------------------------------------------------- /third-party/uswds/img/alerts/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/alerts/info.png -------------------------------------------------------------------------------- /third-party/uswds/img/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/arrow-down.png -------------------------------------------------------------------------------- /third-party/uswds/img/arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/arrow-right.png -------------------------------------------------------------------------------- /third-party/uswds/img/alerts/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/alerts/error.png -------------------------------------------------------------------------------- /third-party/uswds/img/alerts/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/alerts/success.png -------------------------------------------------------------------------------- /third-party/uswds/img/alerts/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/alerts/warning.png -------------------------------------------------------------------------------- /third-party/uswds/img/us_flag_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/us_flag_small.png -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5.1 2 | WORKDIR code 3 | ADD requirements.txt /code/ 4 | RUN pip install -r requirements.txt 5 | ADD . /code/ 6 | -------------------------------------------------------------------------------- /third-party/uswds/img/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/favicons/favicon.ico -------------------------------------------------------------------------------- /third-party/uswds/img/favicons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/favicons/favicon.png -------------------------------------------------------------------------------- /third-party/uswds/img/favicons/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/favicons/favicon-16.png -------------------------------------------------------------------------------- /third-party/uswds/img/favicons/favicon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/favicons/favicon-57.png -------------------------------------------------------------------------------- /third-party/uswds/img/favicons/favicon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/favicons/favicon-72.png -------------------------------------------------------------------------------- /third-party/uswds/img/favicons/favicon-114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/favicons/favicon-114.png -------------------------------------------------------------------------------- /third-party/uswds/img/favicons/favicon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/favicons/favicon-144.png -------------------------------------------------------------------------------- /third-party/uswds/img/favicons/favicon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/favicons/favicon-192.png -------------------------------------------------------------------------------- /third-party/uswds/img/social-icons/png/rss25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/social-icons/png/rss25.png -------------------------------------------------------------------------------- /third-party/uswds/img/social-icons/png/twitter16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/social-icons/png/twitter16.png -------------------------------------------------------------------------------- /third-party/uswds/img/social-icons/png/youtube15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/social-icons/png/youtube15.png -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-bold-webfont.eot -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-bold-webfont.ttf -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-bold-webfont.woff -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-bold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-bold-webfont.woff2 -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-italic-webfont.eot -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-italic-webfont.ttf -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-light-webfont.eot -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-light-webfont.ttf -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-light-webfont.woff -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-bold-webfont.eot -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-bold-webfont.ttf -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-bold-webfont.woff -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-light-webfont.eot -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-light-webfont.ttf -------------------------------------------------------------------------------- /third-party/uswds/img/social-icons/png/facebook25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/img/social-icons/png/facebook25.png -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-italic-webfont.woff -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-italic-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-italic-webfont.woff2 -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-light-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-light-webfont.woff2 -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-regular-webfont.eot -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-regular-webfont.ttf -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-regular-webfont.woff -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-bold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-bold-webfont.woff2 -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-italic-webfont.eot -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-italic-webfont.ttf -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-italic-webfont.woff -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-light-webfont.woff -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-light-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-light-webfont.woff2 -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-regular-webfont.eot -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-regular-webfont.ttf -------------------------------------------------------------------------------- /third-party/uswds/fonts/merriweather-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/merriweather-regular-webfont.woff2 -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-italic-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-italic-webfont.woff2 -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-regular-webfont.woff -------------------------------------------------------------------------------- /third-party/uswds/fonts/sourcesanspro-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18F/acqstackdb/develop/third-party/uswds/fonts/sourcesanspro-regular-webfont.woff2 -------------------------------------------------------------------------------- /acquisitions/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AcquisitionConfig(AppConfig): 5 | name = 'acquisitions' 6 | verbose_name = 'Acquisitions' 7 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SOCIAL_AUTH_GITHUB_TEAM_KEY=YOUR_CLIENT_ID 2 | SOCIAL_AUTH_GITHUB_TEAM_SECRET=YOUR_CLIENT_SECRET 3 | SOCIAL_AUTH_GITHUB_TEAM_ID=YOUR_TEAM_ID 4 | SOCIAL_AUTH_REDIRECT_IS_HTTPS=False # to allow HTTP redirect to http://localhost 5 | -------------------------------------------------------------------------------- /scripts/cf-db.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # This script creates a psql binary for use when ssh-ing into cloud.gov 4 | curl https://s3.amazonaws.com/18f-cf-cli/psql-9.4.4-ubuntu-14.04.tar.gz | tar xvz 5 | ./psql/bin/psql $DATABASE_URL 6 | -------------------------------------------------------------------------------- /acquisitions/tests/test_views.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from acquisitions.views import home 3 | 4 | 5 | @pytest.mark.django_db 6 | def test_home(rf): 7 | request = rf.get('/') 8 | response = home(request) 9 | assert response.status_code == 200 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", "acqstackdb.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /acquisitions/fixtures/tracks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "acquisitions.track", 4 | "pk": 1, 5 | "fields": { 6 | "name": "Internal" 7 | } 8 | }, 9 | { 10 | "model": "acquisitions.track", 11 | "pk": 2, 12 | "fields": { 13 | "name": "External" 14 | } 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Per https://docs.docker.com/compose/startup-order/, we are going to use 4 | # wait-for-it (source: https://github.com/vishnubob/wait-for-it) as a wrapper 5 | # to ensure that postgres is ready to accept connections 6 | ./third-party/wait-for-it/wait-for-it.sh db:5432 7 | 8 | # Now, run the script 9 | python manage.py migrate 10 | python manage.py runserver 0.0.0.0:5000 11 | -------------------------------------------------------------------------------- /third-party/uswds/img/arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | pep8: 3 | enabled: true 4 | eslint: 5 | enabled: true 6 | csslint: 7 | enabled: true 8 | duplication: 9 | enabled: true 10 | exclude_fingerprints: 11 | - 700324494c75296f38cd27a1e79da2fb 12 | config: 13 | languages: 14 | - python 15 | ratings: 16 | paths: 17 | - "**.py" 18 | exclude_paths: 19 | - "third-party/**" 20 | - "acquisitions/migrations/*" 21 | -------------------------------------------------------------------------------- /third-party/uswds/img/social-icons/svg/facebook25.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /third-party/uswds/img/minus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /acquisitions/migrations/0010_merge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-24 22:22 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0008_role_squashed_0010_auto_20160622_0918'), 12 | ('acquisitions', '0009_auto_20160624_1622'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /acquisitions/migrations/0002_auto_20160527_2144.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-27 21:44 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameModel( 16 | old_name='Releases', 17 | new_name='Release', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /third-party/uswds/img/correct8.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /third-party/uswds/img/correct9.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /acquisitions/migrations/0020_auto_20160712_2020.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-07-12 20:20 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0019_auto_20160712_1441'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterUniqueTogether( 16 | name='steptrackthroughmodel', 17 | unique_together=set([('track', 'step')]), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /acqstackdb/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for acqstackdb 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.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "acqstackdb.settings") 15 | from whitenoise.django import DjangoWhiteNoise 16 | 17 | application = get_wsgi_application() 18 | application = DjangoWhiteNoise(application) 19 | -------------------------------------------------------------------------------- /acquisitions/migrations/0015_auto_20160630_0358.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-30 03:58 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0014_auto_20160628_2334'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='track', 17 | name='name', 18 | field=models.CharField(max_length=50), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /acquisitions/migrations/0018_stage_wip_limit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-07-11 16:43 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0017_auto_20160711_1544'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='stage', 17 | name='wip_limit', 18 | field=models.IntegerField(default=0), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /third-party/uswds/img/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /acquisitions/migrations/0016_steptrackthroughmodel_wip_limit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-07-11 13:46 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0015_auto_20160630_0358'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='steptrackthroughmodel', 17 | name='wip_limit', 18 | field=models.IntegerField(default=0), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /acquisitions/templates/acquisitions/new_index.html: -------------------------------------------------------------------------------- 1 | {% extends "acquisitions/layout.html" %} 2 | 3 | {% block content %} 4 |

Forms

5 |
6 |

7 | You can add one of these: 8 |

9 | 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /acquisitions/migrations/0005_auto_20160607_1618.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-07 16:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0004_auto_20160606_1921'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='acquisition', 17 | name='dollars', 18 | field=models.DecimalField(blank=True, decimal_places=2, max_digits=14, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /acquisitions/migrations/0017_auto_20160711_1544.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-07-11 15:44 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0016_steptrackthroughmodel_wip_limit'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='step', 17 | options={}, 18 | ), 19 | migrations.RemoveField( 20 | model_name='step', 21 | name='order', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /acquisitions/migrations/0014_auto_20160628_2334.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-28 23:34 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0013_auto_20160628_2333'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='acquisition', 17 | name='agency', 18 | ), 19 | migrations.RemoveField( 20 | model_name='acquisition', 21 | name='stage', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | codeclimate-test-reporter==0.1.1 2 | coverage==4.1 3 | defusedxml==0.4.1 4 | dj-database-url==0.4.1 5 | Django==1.9.6 6 | django-floppyforms==1.6.2 7 | -e git+https://github.com/mverleg/django-ordered-model.git#egg=django-ordered-model 8 | django-smart-selects==1.2.2 9 | Jinja2==2.8 10 | MarkupSafe==0.23 11 | oauthlib==1.1.1 12 | poirot==0.2.0 13 | psycopg2==2.6.1 14 | py==1.4.31 15 | PyJWT==1.4.0 16 | pytest==2.9.2 17 | pytest-cov==2.2.1 18 | pytest-django==2.9.1 19 | python-social-auth==0.2.19 20 | python3-openid==3.0.10 21 | regex==2016.5.23 22 | requests==2.10.0 23 | requests-oauthlib==0.6.1 24 | six==1.10.0 25 | tqdm==4.7.1 26 | whitenoise==3.2 27 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | image: postgres:latest 5 | environment: 6 | - POSTGRES_DB=acqstackdb 7 | web: 8 | build: . 9 | environment: 10 | - DATABASE_URL=postgres://postgres:postgres@db:5432/acqstackdb 11 | - SOCIAL_AUTH_GITHUB_TEAM_KEY=${SOCIAL_AUTH_GITHUB_TEAM_KEY} 12 | - SOCIAL_AUTH_GITHUB_TEAM_SECRET=${SOCIAL_AUTH_GITHUB_TEAM_SECRET} 13 | - SOCIAL_AUTH_GITHUB_TEAM_ID=${SOCIAL_AUTH_GITHUB_TEAM_ID} 14 | - SOCIAL_AUTH_REDIRECT_IS_HTTPS=False 15 | command: bash run.sh 16 | ports: 17 | - "5000:5000" 18 | volumes: 19 | - .:/code 20 | depends_on: 21 | - db 22 | -------------------------------------------------------------------------------- /acquisitions/fixtures/actors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "acquisitions.actor", 4 | "pk": 1, 5 | "fields": { 6 | "name": "18F" 7 | } 8 | }, 9 | { 10 | "model": "acquisitions.actor", 11 | "pk": 2, 12 | "fields": { 13 | "name": "AAS" 14 | } 15 | }, 16 | { 17 | "model": "acquisitions.actor", 18 | "pk": 3, 19 | "fields": { 20 | "name": "OGC" 21 | } 22 | }, 23 | { 24 | "model": "acquisitions.actor", 25 | "pk": 4, 26 | "fields": { 27 | "name": "AMD" 28 | } 29 | }, 30 | { 31 | "model": "acquisitions.actor", 32 | "pk": 5, 33 | "fields": { 34 | "name": "Client" 35 | } 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /acquisitions/templates/acquisitions/stages.html: -------------------------------------------------------------------------------- 1 | {% extends "acquisitions/layout.html" %} 2 | 3 | {% block content %} 4 | {% load floppyforms %} 5 |

Stages

6 | {% for stage in stages %} 7 |
8 |

{{ stage.order }}) {{ stage }}

9 | {% if not forloop.first %}{% endif %} 10 | {% if not forloop.last %}{% endif %} 11 | {% csrf_token %} 12 | {% form form using %} 13 | {% formfield form.name with value=stage %} 14 | {% endform %} 15 |
16 | {% endfor %} 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /acquisitions/static/acquisitions/css/style.css: -------------------------------------------------------------------------------- 1 | .usa-disclaimer-official { 2 | padding-left: 3rem; 3 | } 4 | 5 | .usa-disclaimer-stage { 6 | padding-right: 3rem; 7 | } 8 | 9 | .required { 10 | color: red; 11 | } 12 | 13 | .green { 14 | color: green; 15 | } 16 | 17 | .red { 18 | color: #e00; 19 | } 20 | 21 | .yellow { 22 | color: yellow; 23 | text-shadow: 0 0 5px black; 24 | } 25 | 26 | .stages-form { 27 | max-width: 100%; 28 | } 29 | 30 | .stages-form > button[type="submit"], h2 { 31 | display: inline-block; 32 | padding: 5px; 33 | font-size: 2rem; 34 | } 35 | 36 | .float-right { 37 | float: right; 38 | } 39 | 40 | .table-link { 41 | display: block; 42 | text-decoration: none; 43 | color: black; 44 | } 45 | -------------------------------------------------------------------------------- /third-party/uswds/img/plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /acquisitions/migrations/0009_auto_20160624_1622.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-24 16:22 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import django.db.models.deletion 7 | import smart_selects.db_fields 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('acquisitions', '0008_auto_20160623_1638'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='acquisition', 19 | name='award_status', 20 | field=smart_selects.db_fields.ChainedForeignKey(chained_field='track', chained_model_field='track', on_delete=django.db.models.deletion.CASCADE, to='acquisitions.AwardStatus'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /acquisitions/migrations/0012_auto_20160628_2017.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-28 20:17 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0011_auto_20160628_2013'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='stage', 17 | options={'ordering': ('order',)}, 18 | ), 19 | migrations.AddField( 20 | model_name='stage', 21 | name='order', 22 | field=models.PositiveIntegerField(db_index=True, default=0, editable=False), 23 | preserve_default=False, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /acquisitions/management/commands/add_teammate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from django.core.management.base import BaseCommand, CommandError 3 | from django.contrib.auth.models import User 4 | 5 | 6 | class Command(BaseCommand): 7 | help = 'Add teammate' 8 | 9 | def add_arguments(self, parser): 10 | parser.add_argument('username', nargs='+', type=str) 11 | 12 | def handle(self, *args, **options): 13 | for username in options['username']: 14 | try: 15 | user = User.objects.get(username=username) 16 | user.is_staff = True 17 | user.is_superuser = True 18 | print('Making %s a superuser!' % username) 19 | user.save() 20 | 21 | except User.DoesNotExist: 22 | print("%s does not exist" % username) 23 | -------------------------------------------------------------------------------- /acquisitions/management/commands/seed_database.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | from acquisitions import factories 3 | 4 | 5 | class Command(BaseCommand): 6 | 7 | def handle(self, *args, **options): 8 | factories.ActorFactory.create_batch(5) 9 | factories.AgencyFactory.create_batch(5) 10 | factories.SubagencyFactory.create_batch(10) 11 | factories.ContractingOfficeFactory.create_batch(2) 12 | factories.ContractingOfficerFactory.create_batch(10) 13 | factories.CORFactory.create_batch(10) 14 | factories.TrackFactory.create_batch(3) 15 | factories.StageFactory.create_batch(6) 16 | factories.StepFactory.create_batch(10) 17 | factories.StepTrackThroughFactory.create_batch(30) 18 | factories.AcquisitionFactory.create_batch(50) 19 | -------------------------------------------------------------------------------- /acquisitions/tests/test_add_teammate.py: -------------------------------------------------------------------------------- 1 | from django.core.management import call_command 2 | from django.test import TestCase 3 | from django.contrib.auth.models import User 4 | 5 | 6 | class AddTeammateTest(TestCase): 7 | 8 | def setUp(self): 9 | self.user = User.objects.create_user(username='test_user', email='', 10 | password='') 11 | self.assertFalse(self.user.is_superuser, 'User *is not* an admin \ 12 | before the command.') 13 | 14 | def test_superuser_command_output(self): 15 | call_command('add_teammate', 'test_user') 16 | self.user = User.objects.get(username='test_user') 17 | self.assertTrue(self.user.is_superuser, 'User *is* an admin \ 18 | after the command.') 19 | -------------------------------------------------------------------------------- /third-party/uswds/img/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /third-party/uswds/img/social-icons/svg/twitter16.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /third-party/wait-for-it/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Giles Hall 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /third-party/uswds/img/alerts/success.svg: -------------------------------------------------------------------------------- 1 | 3 | 12 | 13 | -------------------------------------------------------------------------------- /acquisitions/templates/acquisitions/new.html: -------------------------------------------------------------------------------- 1 | {% extends "acquisitions/layout.html" %} 2 | {% load static from staticfiles %} 3 | 4 | {% block content %} 5 | {% load floppyforms %} 6 |

{{ item }}

7 |
8 | {% csrf_token %} 9 | {% for field in form %} 10 | {% if field.errors %}
{% endif %} 11 | 14 | {% for error in field.errors %} 15 | {{ error }} 16 | {% endfor %} 17 | {% widget field %} 18 | {% if field.help_text %}
{{ field.help_text }}
{% endif %} 19 | {% if field.errors %}
{% endif %} 20 | {% endfor %} 21 |
22 | 23 |
24 |
25 | {% endblock %} 26 | 27 | {% block resources %} 28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /acquisitions/fixtures/stages.json: -------------------------------------------------------------------------------- 1 | [{"model": "acquisitions.stage", "pk": 1, "fields": {"order": 0, "name": "Qualifying", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 3, "fields": {"order": 4, "name": "RFQ Preparation", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 4, "fields": {"order": 5, "name": "RFQ Approval", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 5, "fields": {"order": 2, "name": "Agreement Approval", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 6, "fields": {"order": 1, "name": "Agreement Scoping", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 7, "fields": {"order": 9, "name": "Evaluation Phase", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 8, "fields": {"order": 8, "name": "Proposal Phase", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 9, "fields": {"order": 3, "name": "Staging", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 10, "fields": {"order": 6, "name": "Staging II", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 11, "fields": {"order": 7, "name": "RFQ on Street", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 12, "fields": {"order": 10, "name": "Award Approval", "wip_limit": 0}}, {"model": "acquisitions.stage", "pk": 13, "fields": {"order": 11, "name": "Post-Award", "wip_limit": 0}}] -------------------------------------------------------------------------------- /acquisitions/migrations/0004_auto_20160606_1921.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-06 19:21 3 | from __future__ import unicode_literals 4 | 5 | import django.core.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('acquisitions', '0003_auto_20160531_1702_squashed_0007_auto_20160531_2006'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Vendor', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=200)), 22 | ('email', models.EmailField(max_length=254)), 23 | ('duns', models.CharField(max_length=9, validators=[django.core.validators.RegexValidator(message='DUNS number must be 9 digits', regex='^\\d{9}$')])), 24 | ], 25 | ), 26 | migrations.AddField( 27 | model_name='acquisition', 28 | name='vendor', 29 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Vendor'), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /acquisitions/providers/fake_agency.py: -------------------------------------------------------------------------------- 1 | import random 2 | from faker.providers import BaseProvider 3 | 4 | 5 | class AgencyProvider(BaseProvider): 6 | agency_parts = ( 7 | ( 8 | "Department of", "Office of", "Bureau of", 9 | ), 10 | ( 11 | "the Interior", "Administrating", "Hats", "Management", "Labor", 12 | "Finance", "Departments", "Flying" 13 | ) 14 | ) 15 | 16 | big_agency_start = ( 17 | "Department of", "Office of", "Bureau of", 18 | ) 19 | 20 | big_agency_end = ( 21 | "Administration", "Agency", 22 | ) 23 | 24 | medium_agency_end = ( 25 | "Division", "Section", 26 | ) 27 | 28 | small_agency_end = ( 29 | "Region", "Office", "Room", 30 | ) 31 | 32 | extra_parts = ( 33 | "Synergy", "Failure", "High-Profile Success", "First Aid", "Gravy", 34 | "Sandwiches", "Wine", "Budget", "Style" 35 | ) 36 | 37 | def agency(self, size="large"): 38 | result = [] 39 | for part in self.agency_parts: 40 | result.append(self.random_element(part)) 41 | if random.randint(0, 100) > 70: 42 | result.append("and") 43 | result.append(self.random_element(self.extra_parts)) 44 | 45 | return " ".join(result) 46 | -------------------------------------------------------------------------------- /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 about anything, just ask -- or submit the issue or pull request anyway. The worst that can happen is you'll be politely asked to change something. We love all friendly contributions. 4 | 5 | We want to ensure a welcoming environment for all of our projects. Our staff follow the [18F Code of Conduct](https://github.com/18F/code-of-conduct/blob/master/code-of-conduct.md) and all contributors should do the same. 6 | 7 | We encourage you to read this project's CONTRIBUTING policy (you are here), its [LICENSE](LICENSE.md), and its [README](README.md). 8 | 9 | If you have any questions or want to read more, check out the [18F Open Source Policy GitHub repository]( https://github.com/18f/open-source-policy), or just [shoot us an email](mailto:18f@gsa.gov). 10 | 11 | ## Public domain 12 | 13 | This project is in the public domain within the United States, and 14 | copyright and related rights in the work worldwide are waived through 15 | the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/). 16 | 17 | All contributions to this project will be released under the CC0 18 | dedication. By submitting a pull request, you are agreeing to comply 19 | with this waiver of copyright interest. 20 | -------------------------------------------------------------------------------- /acquisitions/migrations/0006_auto_20160609_1416.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-09 14:16 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0005_auto_20160607_1618'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='agency', 17 | options={'verbose_name_plural': 'Agencies'}, 18 | ), 19 | migrations.AlterModelOptions( 20 | name='contractingoffice', 21 | options={'verbose_name': 'Contracting Office', 'verbose_name_plural': 'Contracting Offices'}, 22 | ), 23 | migrations.AlterModelOptions( 24 | name='contractingofficer', 25 | options={'ordering': ('name',), 'verbose_name': 'Contracting Officer', 'verbose_name_plural': 'Contracting Officers'}, 26 | ), 27 | migrations.AlterModelOptions( 28 | name='cor', 29 | options={'ordering': ('name',), 'verbose_name': 'Contracting Officer Representative', 'verbose_name_plural': 'Contracting Officer Representatives'}, 30 | ), 31 | migrations.AlterModelOptions( 32 | name='subagency', 33 | options={'ordering': ('name',), 'verbose_name_plural': 'Subagencies'}, 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /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 [Legal Code (read the full text)](https://creativecommons.org/publicdomain/zero/1.0/legalcode). 10 | 11 | ### No Copyright 12 | 13 | The person who associated a work with this deed has dedicated the work to 14 | the public domain by waiving all of his or her rights to the work worldwide 15 | under copyright law, including all related and neighboring rights, to the 16 | extent allowed by law. 17 | 18 | You can copy, modify, distribute and perform the work, even for commercial 19 | purposes, all without asking permission. 20 | 21 | ### Other Information 22 | 23 | In no way are the patent or trademark rights of any person affected by CC0, 24 | nor are the rights that other persons may have in the work or in how the 25 | work is used, such as publicity or privacy rights. 26 | 27 | Unless expressly stated otherwise, the person who associated a work with 28 | this deed makes no warranties about the work, and disclaims liability for 29 | all uses of the work, to the fullest extent permitted by applicable law. 30 | When using or citing the work, you should not imply endorsement by the 31 | author or the affirmer. 32 | -------------------------------------------------------------------------------- /third-party/uswds/img/social-icons/svg/rss25.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /acquisitions/migrations/0008_role_squashed_0010_auto_20160622_0918.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-22 09:57 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | replaces = [('acquisitions', '0008_role'), ('acquisitions', '0009_role_acquisition'), ('acquisitions', '0010_auto_20160622_0918')] 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ('acquisitions', '0007_auto_20160617_0357'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Role', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('description', models.CharField(blank=True, choices=[('P', 'Product Lead'), ('A', 'Acquisition Lead'), ('T', 'Technical Lead')], max_length=100, null=True)), 25 | ('teammate', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 26 | ], 27 | ), 28 | migrations.AddField( 29 | model_name='acquisition', 30 | name='roles', 31 | field=models.ManyToManyField(to='acquisitions.Role'), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /acquisitions/migrations/0013_auto_20160628_2333.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-28 23:33 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import smart_selects.db_fields 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('acquisitions', '0012_auto_20160628_2017'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='acquisition', 19 | name='agency', 20 | field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Agency'), 21 | ), 22 | migrations.AlterField( 23 | model_name='acquisition', 24 | name='roles', 25 | field=models.ManyToManyField(blank=True, to='acquisitions.Role'), 26 | ), 27 | migrations.AlterField( 28 | model_name='acquisition', 29 | name='stage', 30 | field=models.ForeignKey(blank=True, default=0, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Stage'), 31 | ), 32 | migrations.AlterField( 33 | model_name='acquisition', 34 | name='step', 35 | field=smart_selects.db_fields.ChainedForeignKey(chained_field='track', chained_model_field='track', on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Step'), 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | /* 3 | * ENVIRONMENTS 4 | * ================= 5 | */ 6 | 7 | // Define globals exposed by modern browsers. 8 | "browser": true, 9 | 10 | // Define globals exposed by jQuery. 11 | "jquery": true, 12 | 13 | // Define globals exposed by Node.js. 14 | "node": true, 15 | 16 | // Allow ES6. 17 | "esversion": 6, 18 | 19 | /* 20 | * ENFORCING OPTIONS 21 | * ================= 22 | */ 23 | 24 | // Force all variable names to use either camelCase style or UPPER_CASE 25 | // with underscores. 26 | "camelcase": true, 27 | 28 | // Prohibit use of == and != in favor of === and !==. 29 | "eqeqeq": true, 30 | 31 | // Enforce tab width of 2 spaces. 32 | "indent": 2, 33 | 34 | // Prohibit use of a variable before it is defined. 35 | "latedef": true, 36 | 37 | // Enforce line length to 100 characters 38 | "maxlen": 100, 39 | 40 | // Require capitalized names for constructor functions. 41 | "newcap": true, 42 | 43 | // Enforce use of single quotation marks for strings. 44 | "quotmark": "single", 45 | 46 | // Enforce placing 'use strict' at the top function scope 47 | "strict": true, 48 | 49 | // Prohibit use of explicitly undeclared variables. 50 | "undef": true, 51 | 52 | // Warn when variables are defined but never used. 53 | "unused": true, 54 | 55 | /* 56 | * RELAXING OPTIONS 57 | * ================= 58 | */ 59 | 60 | // Suppress warnings about == null comparisons. 61 | "eqnull": true 62 | } 63 | -------------------------------------------------------------------------------- /acqstackdb/urls.py: -------------------------------------------------------------------------------- 1 | """acqstackdb URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.8/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Add an import: from blog import urls as blog_urls 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 15 | """ 16 | from django.conf.urls import include, url 17 | from django.contrib import admin 18 | 19 | from acquisitions import views 20 | 21 | urlpatterns = [ 22 | url(r'^$', views.home, name='home'), 23 | url(r'^acquisition/(?P\d*)$', views.acquisition, name='acquisition'), 24 | url(r'^acquisition/(?P\d*)/edit$', 25 | views.edit_acquisition, 26 | name='edit acquisition' 27 | ), 28 | url(r'^stages$', views.stages, name='stages'), 29 | url(r'^new/$', views.new_index, name='new_index'), 30 | url(r'^new/(?P\w*)$', views.new, name="new"), 31 | url(r'^logout/$', views.logout_view, name="logout"), 32 | url(r'^admin/', include(admin.site.urls)), 33 | url('', include('social.apps.django_app.urls', namespace='social')), 34 | url(r'^chaining/', include('smart_selects.urls')), 35 | ] 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # IPython Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | venv/ 87 | ENV/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | 92 | # Rope project settings 93 | .ropeproject 94 | staticfiles 95 | db.sqlite3 96 | acquisitions_manifest.yml 97 | cf-ssh.yml 98 | -------------------------------------------------------------------------------- /acquisitions/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from ordered_model.admin import OrderedModelAdmin, OrderedTabularInline 3 | 4 | # Register your models here. 5 | from .models import Acquisition, Agency, Subagency, ContractingOffice, \ 6 | ContractingOfficer, COR, Evaluator, Release, Vendor, \ 7 | Role, Actor, Step, Track, Stage, StepTrackThroughModel 8 | 9 | 10 | @admin.register(Agency, Subagency, ContractingOffice, ContractingOfficer, COR, 11 | Evaluator, Release, Vendor, Role, Actor, Track) 12 | class AdminAdmin(admin.ModelAdmin): 13 | pass 14 | 15 | 16 | class AcquisitionAdmin(admin.ModelAdmin): 17 | filter_horizontal = ('roles',) 18 | 19 | 20 | class StepTrackThroughModelInline(OrderedTabularInline): 21 | model = StepTrackThroughModel 22 | fields = ('track', 'order', 'wip_limit', 'move_up_down_links',) 23 | readonly_fields = ('order', 'move_up_down_links',) 24 | extra = 1 25 | ordering = ('order',) 26 | 27 | 28 | class StepAdmin(OrderedModelAdmin): 29 | list_display = ('actor', 'stage',) 30 | inlines = (StepTrackThroughModelInline,) 31 | 32 | def get_urls(self): 33 | urls = super(StepAdmin, self).get_urls() 34 | for inline in self.inlines: 35 | if hasattr(inline, 'get_urls'): 36 | urls = inline.get_urls(self) + urls 37 | return urls 38 | 39 | 40 | class StageAdmin(OrderedModelAdmin): 41 | list_display = ('name', 'move_up_down_links') 42 | 43 | admin.site.register(Stage, StageAdmin) 44 | admin.site.register(Acquisition, AcquisitionAdmin) 45 | admin.site.register(Step, StepAdmin) 46 | -------------------------------------------------------------------------------- /third-party/uswds/img/alerts/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # acqstackdb 2 | 3 | `release`: [![Build Status](https://travis-ci.org/18F/acqstackdb.svg?branch=release)](https://travis-ci.org/18F/acqstackdb) 4 | 5 | `develop`: [![Build Status](https://travis-ci.org/18F/acqstackdb.svg?branch=develop)](https://travis-ci.org/18F/acqstackdb) [![Accessibility](https://continua11y.18f.gov/18F/acqstackdb.svg?branch=develop)](https://continua11y.18f.gov/18F/acqstackdb) [![Code Climate](https://codeclimate.com/github/18F/acqstackdb/badges/gpa.svg)](https://codeclimate.com/github/18F/acqstackdb) [![Test Coverage](https://codeclimate.com/github/18F/acqstackdb/badges/coverage.svg)](https://codeclimate.com/github/18F/acqstackdb/coverage) 6 | 7 | This is a Django app built to track the progress of acquisitions by 18F Acquisitions. 8 | 9 | ## Installation 10 | 11 | For information about how to set up the application, view the [setup documentation](documentation/setup.md). 12 | 13 | ## Contributing 14 | 15 | See [CONTRIBUTING](CONTRIBUTING.md) for additional information. 16 | 17 | ### Branch flow 18 | 19 | - Main branch: `release` 20 | - Development branch: `develop` 21 | 22 | ## Public domain 23 | 24 | This project is in the worldwide [public domain](LICENSE.md). As stated in [CONTRIBUTING](CONTRIBUTING.md): 25 | 26 | > This project is in the public domain within the United States, and copyright and related rights in the work worldwide are waived through the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/). 27 | > 28 | > All contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest. 29 | -------------------------------------------------------------------------------- /third-party/uswds/img/alerts/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 17 | 18 | -------------------------------------------------------------------------------- /acquisitions/forms.py: -------------------------------------------------------------------------------- 1 | import floppyforms.__future__ as forms 2 | from acquisitions import models 3 | 4 | 5 | class AcquisitionForm(forms.ModelForm): 6 | class Meta: 7 | model = models.Acquisition 8 | # fields = ['subagency', 'track', 'task', 'step'] 9 | exclude = [] 10 | 11 | 12 | class TrackForm(forms.ModelForm): 13 | class Meta: 14 | model = models.Track 15 | fields = ['name'] 16 | 17 | 18 | class StageForm(forms.ModelForm): 19 | class Meta: 20 | model = models.Stage 21 | fields = ['name'] 22 | 23 | 24 | class HiddenStageForm(StageForm): 25 | name = forms.CharField(widget=forms.HiddenInput()) 26 | 27 | 28 | class StepForm(forms.ModelForm): 29 | track = forms.ModelMultipleChoiceField(queryset=models.Track.objects.all()) 30 | 31 | class Meta: 32 | model = models.Step 33 | exclude = [] 34 | 35 | def save(self, commit=True): 36 | # step = super(StepForm, self).save() 37 | form_data = self.cleaned_data 38 | step = models.Step.objects.create( 39 | stage=form_data["stage"], 40 | actor=form_data["actor"] 41 | ) 42 | for track in form_data["track"]: 43 | models.StepTrackThroughModel.objects.create( 44 | step=step, 45 | track=track 46 | ) 47 | 48 | 49 | class AgencyForm(forms.ModelForm): 50 | class Meta: 51 | model = models.Agency 52 | fields = ['name'] 53 | 54 | 55 | class SubagencyForm(forms.ModelForm): 56 | class Meta: 57 | model = models.Subagency 58 | fields = ['agency', 'name'] 59 | 60 | 61 | class ActorForm(forms.ModelForm): 62 | class Meta: 63 | model = models.Actor 64 | fields = ['name'] 65 | -------------------------------------------------------------------------------- /acquisitions/migrations/0021_auto_20160712_2135.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-07-12 21:35 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0020_auto_20160712_2020'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='agency', 17 | name='abbreviation', 18 | field=models.CharField(blank=True, max_length=10, null=True), 19 | ), 20 | migrations.AddField( 21 | model_name='subagency', 22 | name='abbreviation', 23 | field=models.CharField(blank=True, max_length=10, null=True), 24 | ), 25 | migrations.AlterField( 26 | model_name='agency', 27 | name='cgac_agency_code', 28 | field=models.IntegerField(blank=True, null=True), 29 | ), 30 | migrations.AlterField( 31 | model_name='agency', 32 | name='department', 33 | field=models.CharField(blank=True, max_length=100, null=True), 34 | ), 35 | migrations.AlterField( 36 | model_name='agency', 37 | name='omb_agency_code', 38 | field=models.IntegerField(blank=True, null=True), 39 | ), 40 | migrations.AlterField( 41 | model_name='agency', 42 | name='omb_bureau_code', 43 | field=models.IntegerField(blank=True, null=True), 44 | ), 45 | migrations.AlterField( 46 | model_name='agency', 47 | name='treasury_agency_code', 48 | field=models.IntegerField(blank=True, null=True), 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /acquisitions/fixtures/steps.json: -------------------------------------------------------------------------------- 1 | [{"model": "acquisitions.step", "pk": 1, "fields": {"actor": 1, "stage": 1}}, {"model": "acquisitions.step", "pk": 6, "fields": {"actor": 1, "stage": 6}}, {"model": "acquisitions.step", "pk": 7, "fields": {"actor": 1, "stage": 5}}, {"model": "acquisitions.step", "pk": 8, "fields": {"actor": 2, "stage": 5}}, {"model": "acquisitions.step", "pk": 9, "fields": {"actor": 3, "stage": 5}}, {"model": "acquisitions.step", "pk": 10, "fields": {"actor": 4, "stage": 5}}, {"model": "acquisitions.step", "pk": 11, "fields": {"actor": 5, "stage": 5}}, {"model": "acquisitions.step", "pk": 12, "fields": {"actor": 1, "stage": 9}}, {"model": "acquisitions.step", "pk": 13, "fields": {"actor": 1, "stage": 3}}, {"model": "acquisitions.step", "pk": 14, "fields": {"actor": 1, "stage": 4}}, {"model": "acquisitions.step", "pk": 15, "fields": {"actor": 2, "stage": 4}}, {"model": "acquisitions.step", "pk": 16, "fields": {"actor": 3, "stage": 4}}, {"model": "acquisitions.step", "pk": 17, "fields": {"actor": 4, "stage": 4}}, {"model": "acquisitions.step", "pk": 18, "fields": {"actor": 5, "stage": 4}}, {"model": "acquisitions.step", "pk": 19, "fields": {"actor": 1, "stage": 10}}, {"model": "acquisitions.step", "pk": 20, "fields": {"actor": 1, "stage": 11}}, {"model": "acquisitions.step", "pk": 21, "fields": {"actor": 1, "stage": 8}}, {"model": "acquisitions.step", "pk": 22, "fields": {"actor": 1, "stage": 7}}, {"model": "acquisitions.step", "pk": 27, "fields": {"actor": 1, "stage": 12}}, {"model": "acquisitions.step", "pk": 28, "fields": {"actor": 2, "stage": 12}}, {"model": "acquisitions.step", "pk": 29, "fields": {"actor": 3, "stage": 12}}, {"model": "acquisitions.step", "pk": 30, "fields": {"actor": 4, "stage": 12}}, {"model": "acquisitions.step", "pk": 31, "fields": {"actor": 5, "stage": 12}}, {"model": "acquisitions.step", "pk": 32, "fields": {"actor": 1, "stage": 13}}] -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '3.5' 4 | env: 5 | global: 6 | - CF_USERNAME=18f-acq_deployer 7 | - secure: mHiLq/H/z4qZ7TwYduV+KsO9rccOtxZ+Oi+AFO+hHU4uFMj8cTa7IxB+85DFd+KoQxNVwOLnweLVPWOUzt41I9TVxols5C6N0cx0Gg1aX+177aBBffpb/pGA6CnCcWn41H5VFJPJgCUeANEEmJ9sNu+V45BaIVSEbMPlDaIjkOB4MMX8D00b4q5ik5mJny+gTDDfc/Hdv6bCzVPZIlVYbir93nyCAdjtylkpOWl8uMqMWtH0RrmeFMFh1PdQhBnjupKhFOkL+j4JZcwc6CkPz5wNHdaUI9Rd5jErM4xcSZlTvpDB27pLg6mXvRc6qAhPfg1gtRXFLqPhzk5G4J7dDHhrRGbtihzvDIyDsWc6dKLljDrfZIUonDDorcnL4BB2pDYINqIQVq1WMzijMh0WF0wqqeycZiMLjuxVZA9CgcknvJh1JDCRmKOSe8zpM7CZ1GQ5Q/fo4+TUaAPZFyrx1pNgGKiGjyKCEJtqOA47QLlTSLiULT/ofVGoI9QFYIHjTIlR0OtJS8zUJbwGccGwWJLkZUeZmAw+ohGRTbP3wZwDcLnLTiouS3TN6YTls0Vs82sX9CHyXr06L+bsE6W3LysY0Wa93CreZPFtf7bgpF14rR+LRU29IVbqaXdariaDc97s3oRYb/yPrXmbgiTE6X4/yVFZkLB6UexWuBt5ohc= 8 | addons: 9 | postgresql: '9.4' 10 | install: pip install -r requirements.txt 11 | cache: 12 | directories: 13 | - "$HOME/.cache/pip" 14 | script: "./manage.py collectstatic --noinput -v 0 && py.test --cov && codeclimate-test-reporter" 15 | after_script: 16 | - npm install -g pa11y-crawl 17 | - pa11y-crawl --run "./manage.py runserver" --ci http://localhost:8000 18 | before_deploy: 19 | - export PATH=$HOME:$PATH 20 | - travis_retry curl -L -o $HOME/cf.tgz "https://cli.run.pivotal.io/stable?release=linux64-binary&version=6.15.0" 21 | - tar xzvf $HOME/cf.tgz -C $HOME 22 | - mkdir -p ${HOME}/Godeps/_workspace 23 | - export GOPATH=${HOME}/Godeps/_workspace 24 | - go get github.com/concourse/autopilot 25 | - cf install-plugin -f $GOPATH/bin/autopilot 26 | - travis_retry curl -L -o $HOME/18f.zip "https://github.com/18F/18f-cli/archive/release.zip" 27 | - unzip $HOME/18f.zip -d $HOME/18f-cli 28 | deploy: 29 | - provider: script 30 | script: bash $HOME/18f-cli/18f-cli-release/bin/deploy -o 18f-acq -s staging acquisitions 31 | skip_cleanup: true 32 | on: 33 | branch: develop 34 | -------------------------------------------------------------------------------- /third-party/uswds/img/alerts/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 18 | 19 | -------------------------------------------------------------------------------- /acquisitions/migrations/0019_auto_20160712_1441.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-07-12 14:41 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0018_stage_wip_limit'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='agency', 17 | name='cgac_agency_code', 18 | field=models.IntegerField(null=True), 19 | ), 20 | migrations.AddField( 21 | model_name='agency', 22 | name='department', 23 | field=models.CharField(max_length=100, null=True), 24 | ), 25 | migrations.AddField( 26 | model_name='agency', 27 | name='omb_agency_code', 28 | field=models.IntegerField(null=True), 29 | ), 30 | migrations.AddField( 31 | model_name='agency', 32 | name='omb_bureau_code', 33 | field=models.IntegerField(null=True), 34 | ), 35 | migrations.AddField( 36 | model_name='agency', 37 | name='treasury_agency_code', 38 | field=models.IntegerField(null=True), 39 | ), 40 | migrations.AlterField( 41 | model_name='acquisition', 42 | name='naics', 43 | field=models.IntegerField(blank=True, null=True, verbose_name='NAICS Code'), 44 | ), 45 | migrations.AlterField( 46 | model_name='acquisition', 47 | name='rfq_id', 48 | field=models.IntegerField(blank=True, null=True, verbose_name='RFQ ID'), 49 | ), 50 | migrations.AlterField( 51 | model_name='stage', 52 | name='wip_limit', 53 | field=models.IntegerField(default=0, verbose_name='WIP Limit'), 54 | ), 55 | migrations.AlterField( 56 | model_name='steptrackthroughmodel', 57 | name='wip_limit', 58 | field=models.IntegerField(default=0, verbose_name='WIP Limit'), 59 | ), 60 | ] 61 | -------------------------------------------------------------------------------- /acquisitions/migrations/0008_auto_20160623_1638.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-23 16:38 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('acquisitions', '0007_auto_20160617_0357'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='AwardStatus', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('status', models.CharField(max_length=50)), 21 | ('actor', models.CharField(max_length=50)), 22 | ('ordering', models.IntegerField(editable=False, null=True)), 23 | ('is_before', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='acquisitions.AwardStatus')), 24 | ], 25 | options={ 26 | 'verbose_name_plural': 'Award Statuses', 27 | }, 28 | ), 29 | migrations.CreateModel( 30 | name='Track', 31 | fields=[ 32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 33 | ('name', models.CharField(max_length=30)), 34 | ], 35 | ), 36 | migrations.AlterField( 37 | model_name='acquisition', 38 | name='award_status', 39 | field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.AwardStatus'), 40 | ), 41 | migrations.AddField( 42 | model_name='awardstatus', 43 | name='track', 44 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Track'), 45 | ), 46 | migrations.AddField( 47 | model_name='acquisition', 48 | name='track', 49 | field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Track'), 50 | preserve_default=False, 51 | ), 52 | ] 53 | -------------------------------------------------------------------------------- /acquisitions/tests/test_team.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.contrib.auth.models import User 3 | from acquisitions.models import Acquisition, Agency, Subagency,\ 4 | Vendor, Step, StepTrackThroughModel, Stage,\ 5 | Track, Actor, Role 6 | 7 | 8 | @pytest.fixture 9 | def test_user(): 10 | user = User.objects.create_user(username='test_user', 11 | email='', password='') 12 | return user 13 | 14 | 15 | @pytest.fixture 16 | def role(test_user): 17 | role = Role.objects.create(description='A', teammate=test_user) 18 | return role 19 | 20 | 21 | @pytest.fixture 22 | def acquisition(): 23 | agency = Agency.objects.create(name="Test Agency") 24 | subagency = Subagency.objects.create(name="Test Subagency", agency=agency) 25 | track = Track.objects.create(name="Test Track") 26 | stage = Stage.objects.create(name="Test Stage") 27 | actor = Actor.objects.create(name="Test Actor") 28 | step = Step.objects.create( 29 | stage=stage, 30 | actor=actor 31 | ) 32 | through = StepTrackThroughModel.objects.create( 33 | step=step, 34 | track=track 35 | ) 36 | acquisition = Acquisition.objects.create( 37 | subagency=subagency, 38 | task="Build a test thing", 39 | step=step, 40 | track=track 41 | ) 42 | return acquisition 43 | 44 | 45 | @pytest.mark.django_db 46 | def test_role_created(role, test_user): 47 | assert isinstance(role, Role) 48 | assert role.description == 'A' 49 | assert role.teammate == test_user 50 | 51 | 52 | @pytest.mark.django_db 53 | def test_add_role_to_acquisition(acquisition, role): 54 | ''' 55 | Test to see whether a role object is properly added to the acquisition 56 | ''' 57 | assert isinstance(acquisition, Acquisition) 58 | assert len(acquisition.roles.all()) == 0 # Confirm no existing roles 59 | 60 | # Add the role 61 | acquisition.roles.add(role) 62 | 63 | # Confirm that the role is added 64 | results = acquisition.roles.all() 65 | assert len(results) == 1 66 | assert results[0] == role 67 | assert str(results[0]) == "Acquisition Lead - test_user" 68 | -------------------------------------------------------------------------------- /documentation/setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | There are two methods of setting up the acqstackdb application: 4 | 5 | 1. Local installation; and 6 | 2. Using docker-compose. 7 | 8 | ## Local Installation 9 | 10 | This app is designed to run on Python 3.5.1. `pyenv` is recommended for managing 11 | your Python version, along with `pyenv-virtualenvwrapper` for managing the 12 | dependencies installed with `pip`. With that, you can prepare your development 13 | environment by running: 14 | 15 | ``` 16 | git clone https://github.com/18f/acqstackdb.git 17 | cd acqstackdb 18 | createdb acqstackdb 19 | ./manage.py migrate 20 | mkvirtualenv acqstackdb 21 | pip install -r requirements.txt 22 | ``` 23 | 24 | Authentication is managed via GitHub OAuth, with access limited to a specified 25 | GitHub team. First, you'll need a [GitHub 26 | application](https://github.com/settings/applications/new). The callback URL for 27 | the application is `[your-url]/complete/github-team`. Next, you'll need a 28 | GitHub organization and [a team within 29 | it](https://help.github.com/articles/setting-up-teams/). Getting the team's ID 30 | is a bit tricky, unfortunately, and involves [querying the GitHub 31 | API](https://developer.github.com/v3/orgs/teams/#list-teams). 32 | 33 | At this point, you'll have the GitHub application's `Client ID` and `Client 34 | Secret`, along with your team's `ID`. Now, run the following: 35 | 36 | ``` 37 | export SOCIAL_AUTH_GITHUB_TEAM_KEY=YOUR_CLIENT_ID 38 | export SOCIAL_AUTH_GITHUB_TEAM_SECRET=YOUR_CLIENT_SECRET 39 | export SOCIAL_AUTH_GITHUB_TEAM_ID=YOUR_TEAM_ID 40 | export SOCIAL_AUTH_REDIRECT_IS_HTTPS=False # to allow HTTP redirect to http://localhost 41 | ./manage.py runserver 42 | ``` 43 | 44 | The app should now be running at http://localhost:8000. 45 | 46 | If you'd like to load some starter data, you have two options: fixtures and factories. To set up a skeleton that looks like our production site, `./manage.py loaddata acquisitions/fixtures/*.json` will pull in the tracks, stages, steps, and actors. For a wider-ranging randomized setup, you can use `./manage.py seed_database`. 47 | 48 | ## Using docker-compose 49 | 50 | To use [docker-compose](https://docs.docker.com/compose/), you'll need to run the folloiwng commands: 51 | 52 | ``` 53 | git clone https://github.com/18f/acqstackdb.git 54 | cd acqstackdb 55 | mv .env.example .env 56 | ``` 57 | 58 | Then, edit the `.env` file with the GitHub application's `Client ID` and `Client 59 | Secret`, along with your team's `ID`. Once, you done this, run: 60 | 61 | ``` 62 | docker-compose up 63 | ``` 64 | -------------------------------------------------------------------------------- /third-party/uswds/img/social-icons/svg/youtube15.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /acquisitions/templates/acquisitions/includes/item.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ acq.task }} ({{ acq.subagency }})

3 |
Description{{ acq.description | default:"—" }}
4 |
Value{{ acq.dollars | default:"—"}}
5 |
Award Date{{ acq.award_date | default:"—" }}
6 | {% if acq.award_status > 17 %} 7 |
Vendor{{ acq.vendor | default:"—" }}
8 | {% endif %} 9 |
Delivery Date{{ acq.delivery_date | default:"—" }}
10 |
Period of Performance{{ acq.period_of_performance | default:"—" }}
11 |
Contracting Office{{ acq.contracting_office | default:"—" }}
12 |
Contracting Officer{{ acq.contracting_officer | default:"—"}}
13 |
COR{{ acq.cor | default:"—" }}
14 |
Product Owner{{ acq.product_owner | default:"—" }}
15 |
Set-Aside Status{{ acq.set_aside_status | default:"—" }}
16 |
Competition Strategy{{ acq.amount_of_competition | default:"—" }}
17 |
Contract Type{{ acq.contract_type | default:"—" }}
18 |
NAICS Code{{ acq.naics | default:"—" }}
19 |
Procurement Method{{ acq.procurement_method | default:"—" }}
20 |
21 | -------------------------------------------------------------------------------- /.about.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # .about.yml project metadata 3 | # 4 | # Short name that acts as the project identifier (required) 5 | name: 6 | 7 | # Full proper name of the project (required) 8 | full_name: 9 | 10 | # The type of content in the repo 11 | # values: app, docs, policy 12 | type: 13 | 14 | # Describes whether a project team, working group/guild, etc. owns the repo (required) 15 | # values: guild, working-group, project 16 | owner_type: 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, sunset, transfer, end 23 | stage: 24 | 25 | # Description of the project 26 | description: 27 | 28 | # Tags that describe the project or aspects of the project 29 | tags: 30 | - 31 | 32 | # Should this repo have automated tests? If so, set to `true`. (required) 33 | # values: true, false 34 | testable: 35 | 36 | # Team members contributing to the project (required) 37 | # Items: 38 | # - github: GitHub user name 39 | # id: Internal team identifier/user name 40 | # role: Team member's role; leads should be designated as 'lead' 41 | team: 42 | - 43 | 44 | # Partners for whom the project is developed 45 | partners: 46 | - 47 | 48 | # Organizations or individuals who have adopted the project for their own use 49 | # Items: 50 | # - id: The name of the organization or individual 51 | # url: A URL to the user's version of the project 52 | users: 53 | - 54 | 55 | # Brief descriptions of significant project developments 56 | milestones: 57 | - 58 | 59 | # Technologies used to build the project 60 | stack: 61 | - 62 | 63 | # Brief description of the project's outcomes 64 | impact: 65 | 66 | # Services used to supply project status information 67 | # Items: 68 | # - name: Name of the service 69 | # category: Type of the service 70 | # url: URL for detailed information 71 | # badge: URL for the status badge 72 | services: 73 | - 74 | 75 | # Licenses that apply to the project and/or its components (required) 76 | # Items by property name pattern: 77 | # .*: 78 | # name: Name of the license from the Software Package Data Exchange (SPDX): https://spdx.org/licenses/ 79 | # url: URL for the text of the license 80 | licenses: 81 | placeholder_label: 82 | 83 | # Tag to use when blogging about this project 84 | blogTag: 85 | - 86 | 87 | # Links to project artifacts 88 | # Items: 89 | # - url: URL for the link 90 | # text: Anchor text for the link 91 | # category: Type of the link 92 | links: 93 | - 94 | 95 | # URIs for points-of-contact 96 | # Items: 97 | # - url: URL for the link 98 | # text: Anchor text for the link 99 | contact: 100 | - 101 | -------------------------------------------------------------------------------- /third-party/wait-for-it/README.md: -------------------------------------------------------------------------------- 1 | `wait-for-it.sh` is a pure bash script that will wait on the availability of a host and TCP port. It is useful for synchronizing the spin-up of interdependent services, such as linked docker containers. Since it is a pure bash script, it does not have any external dependencies. 2 | 3 | ## Usage 4 | 5 | ``` 6 | wait-for-it.sh host:port [-s] [-t timeout] [-- command args] 7 | -h HOST | --host=HOST Host or IP under test 8 | -p PORT | --port=PORT TCP port under test 9 | Alternatively, you specify the host and port as host:port 10 | -s | --strict Only execute subcommand if the test succeeds 11 | -q | --quiet Don't output any status messages 12 | -t TIMEOUT | --timeout=TIMEOUT 13 | Timeout in seconds, zero for no timeout 14 | -- COMMAND ARGS Execute command with args after the test finishes 15 | ``` 16 | 17 | ## Examples 18 | 19 | For example, let's test to see if we can access port 80 on www.google.com, and if it is available, echo the message `google is up`. 20 | 21 | ``` 22 | $ ./wait-for-it.sh www.google.com:80 -- echo "google is up" 23 | wait-for-it.sh: waiting 15 seconds for www.google.com:80 24 | wait-for-it.sh: www.google.com:80 is available after 0 seconds 25 | google is up 26 | ``` 27 | 28 | You can set your own timeout with the `-t` or `--timeout=` option. Setting the timeout value to 0 will disable the timeout: 29 | 30 | ``` 31 | $ ./wait-for-it.sh -t 0 www.google.com:80 -- echo "google is up" 32 | wait-for-it.sh: waiting for www.google.com:80 without a timeout 33 | wait-for-it.sh: www.google.com:80 is available after 0 seconds 34 | google is up 35 | ``` 36 | 37 | The subcommand will be executed regardless if the service is up or not. If you wish to execute the subcommand only if the service is up, add the `--strict` argument. In this example, we will test port 81 on www.google.com which will fail: 38 | 39 | ``` 40 | $ ./wait-for-it.sh www.google.com:81 --timeout=1 --strict -- echo "google is up" 41 | wait-for-it.sh: waiting 1 seconds for www.google.com:81 42 | wait-for-it.sh: timeout occurred after waiting 1 seconds for www.google.com:81 43 | wait-for-it.sh: strict mode, refusing to execute subprocess 44 | ``` 45 | 46 | If you don't want to execute a subcommand, leave off the `--` argument. This way, you can test the exit condition of `wait-for-it.sh` in your own scripts, and determine how to proceed: 47 | 48 | ``` 49 | $ ./wait-for-it.sh www.google.com:80 50 | wait-for-it.sh: waiting 15 seconds for www.google.com:80 51 | wait-for-it.sh: www.google.com:80 is available after 0 seconds 52 | $ echo $? 53 | 0 54 | $ ./wait-for-it.sh www.google.com:81 55 | wait-for-it.sh: waiting 15 seconds for www.google.com:81 56 | wait-for-it.sh: timeout occurred after waiting 15 seconds for www.google.com:81 57 | $ echo $? 58 | 124 59 | ``` 60 | -------------------------------------------------------------------------------- /acquisitions/templates/acquisitions/layout.html: -------------------------------------------------------------------------------- 1 | {% load static from staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}Acqstack DB{% endblock %} 8 | 9 | 10 | 11 | 12 | {% block resources %}{% endblock %} 13 | 14 | 15 | 24 |
25 |
26 | {% block content %}{% endblock %} 27 |
28 |
29 |
30 | 33 | 53 | 54 | 62 |
63 | {% block script %}{% endblock %} 64 | 65 | 66 | -------------------------------------------------------------------------------- /acquisitions/tests/test_models.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.core.exceptions import ValidationError 3 | from acquisitions.models import Acquisition, Agency, Subagency,\ 4 | Vendor, Step, StepTrackThroughModel, Stage,\ 5 | Track, Actor 6 | 7 | 8 | @pytest.mark.django_db 9 | def test_create_acquisition(): 10 | agency = Agency.objects.create(name="Test Agency") 11 | subagency = Subagency.objects.create(name="Test Subagency", agency=agency) 12 | track = Track.objects.create(name="Test Track") 13 | stage = Stage.objects.create(name="Test Stage") 14 | actor = Actor.objects.create(name="Test Actor") 15 | step = Step.objects.create( 16 | stage=stage, 17 | actor=actor 18 | ) 19 | through = StepTrackThroughModel.objects.create( 20 | step=step, 21 | track=track 22 | ) 23 | acquisition = Acquisition.objects.create( 24 | subagency=subagency, 25 | task="Build a test thing", 26 | step=step, 27 | track=track 28 | ) 29 | 30 | assert str(acquisition) == "Build a test thing " + \ 31 | "(Test Agency - Test Subagency)" 32 | 33 | 34 | @pytest.mark.django_db 35 | def test_correct_track(): 36 | with pytest.raises(ValidationError): 37 | agency = Agency.objects.create(name="Test Agency") 38 | subagency = Subagency.objects.create(name="Test Subagency", 39 | agency=agency) 40 | track = Track.objects.create(name="Test Track") 41 | track2 = Track.objects.create(name="The Other Track") 42 | stage = Stage.objects.create(name="Test Stage") 43 | actor = Actor.objects.create(name="Test Actor") 44 | step = Step.objects.create( 45 | stage=stage, 46 | actor=actor 47 | ) 48 | through = StepTrackThroughModel.objects.create( 49 | step=step, 50 | track=track2 51 | ) 52 | acquisition = Acquisition.objects.create( 53 | subagency=subagency, 54 | task="Build a test thing", 55 | step=step, 56 | track=track 57 | ) 58 | 59 | acquisition.full_clean() 60 | assert str(acquisition) == "Build a test thing " + \ 61 | "(Test Agency - Test Subagency)" 62 | 63 | 64 | @pytest.mark.django_db 65 | def test_create_vendor(): 66 | vendor = Vendor.objects.create( 67 | name="Test Vendor", 68 | email="testvendor@fake.biz", 69 | duns=123456789 70 | ) 71 | 72 | vendor.full_clean() 73 | assert str(vendor) == "Test Vendor" 74 | 75 | 76 | @pytest.mark.django_db 77 | def test_bad_duns(): 78 | with pytest.raises(ValidationError): 79 | bad_duns_vendor = Vendor.objects.create( 80 | name="Bad DUNS Vendor", 81 | email="testvendor@fake.biz", 82 | duns=666 83 | ) 84 | 85 | bad_duns_vendor.full_clean() 86 | assert str(bad_duns_vendor) == "Bad DUNS Vendor" 87 | -------------------------------------------------------------------------------- /acquisitions/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from acquisitions import models 3 | from acquisitions.providers import fake_agency 4 | 5 | factory.Faker.add_provider(fake_agency.AgencyProvider) 6 | 7 | 8 | class AgencyFactory(factory.django.DjangoModelFactory): 9 | class Meta: 10 | model = models.Agency 11 | 12 | name = factory.Faker("agency") 13 | 14 | 15 | class SubagencyFactory(factory.django.DjangoModelFactory): 16 | class Meta: 17 | model = models.Subagency 18 | 19 | name = factory.Faker("agency", size="medium") 20 | agency = factory.Iterator(models.Agency.objects.all()) 21 | 22 | 23 | class ContractingOfficeFactory(factory.django.DjangoModelFactory): 24 | class Meta: 25 | model = models.ContractingOffice 26 | 27 | name = factory.Faker("agency", size="small") 28 | 29 | 30 | class ContractingOfficerFactory(factory.django.DjangoModelFactory): 31 | class Meta: 32 | model = models.ContractingOfficer 33 | 34 | name = factory.Faker("name") 35 | contracting_office = factory.Iterator( 36 | models.ContractingOffice.objects.all() 37 | ) 38 | 39 | 40 | class CORFactory(factory.django.DjangoModelFactory): 41 | class Meta: 42 | model = models.COR 43 | 44 | name = factory.Faker("name") 45 | 46 | 47 | class TrackFactory(factory.django.DjangoModelFactory): 48 | class Meta: 49 | model = models.Track 50 | 51 | name = factory.Faker("bs") 52 | 53 | 54 | class StageFactory(factory.django.DjangoModelFactory): 55 | class Meta: 56 | model = models.Stage 57 | 58 | name = factory.Faker("bs") 59 | wip_limit = factory.Faker("random_digit_not_null") 60 | 61 | 62 | class ActorFactory(factory.django.DjangoModelFactory): 63 | class Meta: 64 | model = models.Actor 65 | 66 | name = factory.Faker("company") 67 | 68 | 69 | class StepFactory(factory.django.DjangoModelFactory): 70 | class Meta: 71 | model = models.Step 72 | 73 | stage = factory.Iterator(models.Stage.objects.all()) 74 | actor = factory.Iterator(models.Actor.objects.all()) 75 | 76 | 77 | class StepTrackThroughFactory(factory.django.DjangoModelFactory): 78 | class Meta: 79 | model = models.StepTrackThroughModel 80 | 81 | step = factory.Iterator(models.Step.objects.all()) 82 | track = factory.Iterator(models.Track.objects.all()) 83 | wip_limit = factory.Faker("random_digit_not_null") 84 | 85 | 86 | class TrackWithStepFactory(TrackFactory): 87 | together = factory.RelatedFactory(StepTrackThroughFactory, 'track') 88 | 89 | 90 | class VendorFactory(factory.django.DjangoModelFactory): 91 | pass 92 | 93 | 94 | class RoleFactory(factory.django.DjangoModelFactory): 95 | pass 96 | 97 | 98 | class AcquisitionFactory(factory.django.DjangoModelFactory): 99 | class Meta: 100 | model = models.Acquisition 101 | 102 | task = factory.Faker("catch_phrase") 103 | step = factory.Iterator(models.Step.objects.all()) 104 | subagency = factory.Iterator(models.Subagency.objects.all()) 105 | track = factory.Iterator(models.Track.objects.all()) 106 | 107 | 108 | class EvaluatorFactory(factory.django.DjangoModelFactory): 109 | pass 110 | 111 | 112 | class ReleaseFactory(factory.django.DjangoModelFactory): 113 | pass 114 | -------------------------------------------------------------------------------- /acquisitions/templates/acquisitions/index.html: -------------------------------------------------------------------------------- 1 | {% extends "acquisitions/layout.html" %} 2 | 3 | {% block content %} 4 |

Ongoing Acquisitions

5 | {% if request.user.is_authenticated %} 6 | 9 |
10 | Add an acquisition 11 | Add other stuff 12 |
13 |
14 |

Tracks

15 | {% for track in data %}{{track}}{% endfor %} 16 |
17 | {% for track in data.items %} 18 |
19 | 20 | 21 | 22 | 23 | {% for stage, info in track.1.items %} 24 | 27 | {% endfor %} 28 | 29 | 30 | 31 | {% for actor in actors %} 32 | 33 | 36 | {% for stage in track.1.items %} 37 | 48 | {% endfor %} 49 | 50 | {% endfor %} 51 | 52 |
25 | {{ stage }} 26 |
34 | {{ actor.name }} 35 | 38 | {% for step in stage.1.steps.items %} 39 | {% if step.0 == actor.name %} 40 | {% if track.0 == "Overall" %} 41 | {{ step.1.acquisitions|length }} 42 | {% else %} 43 | {{ step.1.acquisitions|length }}{% if step.1.wip_limit == 0 or step.1.wip_limit > step.1.acquisitions|length %} ✔︎{% elif step.1.wip_limit == step.1.acquisitions|length %} ●{% else %} 𝘅{% endif %} 44 | {% endif %} 45 | {% endif %} 46 | {% endfor %} 47 |
53 | {% for stage in track.1.items %} 54 | {% for step in stage.1.steps.items %} 55 |

{{ stage.0 }} - {{ step.0 }}

56 | {% if step.1.acquisitions|length > 0 %} 57 | {% for acq in step.1.acquisitions %} 58 | {% include "acquisitions/includes/item.html" %} 59 | {% endfor %} 60 | {% else %} 61 |
62 |

None

63 |
64 | {% endif %} 65 | {% endfor %} 66 | {% endfor %} 67 |
68 | {% endfor %} 69 | {% else %} 70 | Log in 71 | {% endif %} 72 | {% endblock %} 73 | 74 | {% block script %} 75 | 96 | {% endblock %} 97 | -------------------------------------------------------------------------------- /acquisitions/migrations/0011_auto_20160628_2013.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-28 20:13 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import smart_selects.db_fields 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('acquisitions', '0010_merge'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Actor', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=200)), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='Stage', 26 | fields=[ 27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 28 | ('name', models.CharField(max_length=50)), 29 | ], 30 | ), 31 | migrations.CreateModel( 32 | name='Step', 33 | fields=[ 34 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 35 | ('order', models.PositiveIntegerField(db_index=True, editable=False)), 36 | ('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Actor')), 37 | ('stage', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Stage')), 38 | ], 39 | options={ 40 | 'ordering': ('order',), 41 | 'abstract': False, 42 | }, 43 | ), 44 | migrations.CreateModel( 45 | name='StepTrackThroughModel', 46 | fields=[ 47 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 48 | ('order', models.PositiveIntegerField(db_index=True, editable=False)), 49 | ('step', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Step')), 50 | ('track', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Track')), 51 | ], 52 | options={ 53 | 'ordering': ('track', 'order'), 54 | }, 55 | ), 56 | migrations.RemoveField( 57 | model_name='awardstatus', 58 | name='is_before', 59 | ), 60 | migrations.RemoveField( 61 | model_name='awardstatus', 62 | name='track', 63 | ), 64 | migrations.RemoveField( 65 | model_name='acquisition', 66 | name='award_status', 67 | ), 68 | migrations.AlterField( 69 | model_name='acquisition', 70 | name='track', 71 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='acquisition_track', to='acquisitions.Track'), 72 | ), 73 | migrations.DeleteModel( 74 | name='AwardStatus', 75 | ), 76 | migrations.AddField( 77 | model_name='step', 78 | name='track', 79 | field=models.ManyToManyField(through='acquisitions.StepTrackThroughModel', to='acquisitions.Track'), 80 | ), 81 | migrations.AddField( 82 | model_name='acquisition', 83 | name='stage', 84 | field=smart_selects.db_fields.ChainedForeignKey(chained_field='track', chained_model_field='track', default=0, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Stage'), 85 | ), 86 | migrations.AddField( 87 | model_name='acquisition', 88 | name='step', 89 | field=smart_selects.db_fields.ChainedForeignKey(chained_field='stage', chained_model_field='stage', default=0, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Step'), 90 | ), 91 | ] 92 | -------------------------------------------------------------------------------- /acquisitions/fixtures/steptrack.json: -------------------------------------------------------------------------------- 1 | [{"model": "acquisitions.steptrackthroughmodel", "pk": 7, "fields": {"order": 0, "track": 1, "step": 1, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 8, "fields": {"order": 0, "track": 2, "step": 1, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 18, "fields": {"order": 5, "track": 2, "step": 6, "wip_limit": 4}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 20, "fields": {"order": 6, "track": 2, "step": 7, "wip_limit": 6}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 22, "fields": {"order": 7, "track": 2, "step": 8, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 28, "fields": {"order": 8, "track": 2, "step": 11, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 29, "fields": {"order": 5, "track": 1, "step": 12, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 30, "fields": {"order": 9, "track": 2, "step": 12, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 31, "fields": {"order": 6, "track": 1, "step": 13, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 32, "fields": {"order": 10, "track": 2, "step": 13, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 33, "fields": {"order": 7, "track": 1, "step": 14, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 34, "fields": {"order": 11, "track": 2, "step": 14, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 36, "fields": {"order": 12, "track": 2, "step": 15, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 37, "fields": {"order": 8, "track": 1, "step": 16, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 39, "fields": {"order": 9, "track": 1, "step": 17, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 41, "fields": {"order": 10, "track": 1, "step": 18, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 42, "fields": {"order": 13, "track": 2, "step": 18, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 43, "fields": {"order": 11, "track": 1, "step": 19, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 44, "fields": {"order": 14, "track": 2, "step": 19, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 45, "fields": {"order": 12, "track": 1, "step": 20, "wip_limit": 2}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 46, "fields": {"order": 15, "track": 2, "step": 20, "wip_limit": 2}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 47, "fields": {"order": 13, "track": 1, "step": 21, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 48, "fields": {"order": 16, "track": 2, "step": 21, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 49, "fields": {"order": 14, "track": 1, "step": 22, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 50, "fields": {"order": 17, "track": 2, "step": 22, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 59, "fields": {"order": 19, "track": 1, "step": 27, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 60, "fields": {"order": 22, "track": 2, "step": 27, "wip_limit": 3}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 62, "fields": {"order": 23, "track": 2, "step": 28, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 63, "fields": {"order": 20, "track": 1, "step": 29, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 64, "fields": {"order": 24, "track": 2, "step": 29, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 65, "fields": {"order": 21, "track": 1, "step": 30, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 67, "fields": {"order": 22, "track": 1, "step": 31, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 68, "fields": {"order": 25, "track": 2, "step": 31, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 69, "fields": {"order": 23, "track": 1, "step": 32, "wip_limit": 0}}, {"model": "acquisitions.steptrackthroughmodel", "pk": 70, "fields": {"order": 26, "track": 2, "step": 32, "wip_limit": 0}}] -------------------------------------------------------------------------------- /acquisitions/templates/acquisitions/acquisition.html: -------------------------------------------------------------------------------- 1 | {% extends "acquisitions/layout.html" %} 2 | 3 | {% block content %} 4 |

{{acquisition.task}} Edit

5 | 6 |

Status: {{acquisition.step}}

7 | 8 |

{{ acquisition.get_award_status_display }}

9 | 10 | 11 | 12 | {% for status in statuses %} 13 | 16 | {% endfor %} 17 | 18 | 19 |
14 | {% if status == acquisition.step %} X {% else %} - {% endif %} 15 |
20 | 21 |

Details

22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
Client{{ acquisition.subagency }}
Product Owner{{ acquisition.product_owner|default:'TBD' }}
Details{{ acquisition.details|default:'TBD' }}
39 | 40 |
Track{{ acquisition.track }}
NAICS{{ acquisition.naics|default:'TBD' }}
Team{% for member in acquisition.roles.all %} 53 | {% if forloop.last %}{{ member }}{% else %}{{ member }}
{% endif %} 54 | {% endfor %}
Contracting Officer{{ acquisition.contracting_officer|default:'TBD' }}
Contracting Office{{ acquisition.contracting_office|default:'TBD' }}
COR{{ acquisition.contracting_officer_representative|default:'TBD' }}
Vendor{{ acquisition.vendor|default:'TBD' }}
RFQ Number{{ acquisition.rfq_id|default:'TBD' }}
Period of Performance{{ acquisition.period_of_performance|default:'TBD' }}
Cost{{ acquisition.dollars|default:'TBD' }}
Set-Aside Status{{ acquisition.set_aside_status|default:'TBD' }}
Amount of Competition{{ acquisition.amount_of_competition|default:'TBD' }}
Contract Type{{ acquisition.contract_type|default:'TBD' }}
Competition Strategy{{ acquisition.competition_strategy|default:'TBD' }}
Procurement Method{{ acquisition.procurement_method|default:'TBD' }}
Award Date{{ acquisition.award_date|default:'TBD' }}
Delivery Date{{ acquisition.delivery_date|default:'TBD' }}
114 | {% endblock %} 115 | -------------------------------------------------------------------------------- /third-party/wait-for-it/wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to test if a given TCP host/port are available 3 | 4 | cmdname=$(basename $0) 5 | 6 | echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } 7 | 8 | usage() 9 | { 10 | cat << USAGE >&2 11 | Usage: 12 | $cmdname host:port [-s] [-t timeout] [-- command args] 13 | -h HOST | --host=HOST Host or IP under test 14 | -p PORT | --port=PORT TCP port under test 15 | Alternatively, you specify the host and port as host:port 16 | -s | --strict Only execute subcommand if the test succeeds 17 | -q | --quiet Don't output any status messages 18 | -t TIMEOUT | --timeout=TIMEOUT 19 | Timeout in seconds, zero for no timeout 20 | -- COMMAND ARGS Execute command with args after the test finishes 21 | USAGE 22 | exit 1 23 | } 24 | 25 | wait_for() 26 | { 27 | if [[ $TIMEOUT -gt 0 ]]; then 28 | echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" 29 | else 30 | echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" 31 | fi 32 | start_ts=$(date +%s) 33 | while : 34 | do 35 | (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 36 | result=$? 37 | if [[ $result -eq 0 ]]; then 38 | end_ts=$(date +%s) 39 | echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" 40 | break 41 | fi 42 | sleep 1 43 | done 44 | return $result 45 | } 46 | 47 | wait_for_wrapper() 48 | { 49 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 50 | if [[ $QUIET -eq 1 ]]; then 51 | timeout $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 52 | else 53 | timeout $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 54 | fi 55 | PID=$! 56 | trap "kill -INT -$PID" INT 57 | wait $PID 58 | RESULT=$? 59 | if [[ $RESULT -ne 0 ]]; then 60 | echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" 61 | fi 62 | return $RESULT 63 | } 64 | 65 | # process arguments 66 | while [[ $# -gt 0 ]] 67 | do 68 | case "$1" in 69 | *:* ) 70 | hostport=(${1//:/ }) 71 | HOST=${hostport[0]} 72 | PORT=${hostport[1]} 73 | shift 1 74 | ;; 75 | --child) 76 | CHILD=1 77 | shift 1 78 | ;; 79 | -q | --quiet) 80 | QUIET=1 81 | shift 1 82 | ;; 83 | -s | --strict) 84 | STRICT=1 85 | shift 1 86 | ;; 87 | -h) 88 | HOST="$2" 89 | if [[ $HOST == "" ]]; then break; fi 90 | shift 2 91 | ;; 92 | --host=*) 93 | HOST="${1#*=}" 94 | shift 1 95 | ;; 96 | -p) 97 | PORT="$2" 98 | if [[ $PORT == "" ]]; then break; fi 99 | shift 2 100 | ;; 101 | --port=*) 102 | PORT="${1#*=}" 103 | shift 1 104 | ;; 105 | -t) 106 | TIMEOUT="$2" 107 | if [[ $TIMEOUT == "" ]]; then break; fi 108 | shift 2 109 | ;; 110 | --timeout=*) 111 | TIMEOUT="${1#*=}" 112 | shift 1 113 | ;; 114 | --) 115 | shift 116 | CLI="$@" 117 | break 118 | ;; 119 | --help) 120 | usage 121 | ;; 122 | *) 123 | echoerr "Unknown argument: $1" 124 | usage 125 | ;; 126 | esac 127 | done 128 | 129 | if [[ "$HOST" == "" || "$PORT" == "" ]]; then 130 | echoerr "Error: you need to provide a host and port to test." 131 | usage 132 | fi 133 | 134 | TIMEOUT=${TIMEOUT:-15} 135 | STRICT=${STRICT:-0} 136 | CHILD=${CHILD:-0} 137 | QUIET=${QUIET:-0} 138 | 139 | if [[ $CHILD -gt 0 ]]; then 140 | wait_for 141 | RESULT=$? 142 | exit $RESULT 143 | else 144 | if [[ $TIMEOUT -gt 0 ]]; then 145 | wait_for_wrapper 146 | RESULT=$? 147 | else 148 | wait_for 149 | RESULT=$? 150 | fi 151 | fi 152 | 153 | if [[ $CLI != "" ]]; then 154 | if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then 155 | echoerr "$cmdname: strict mode, refusing to execute subprocess" 156 | exit $RESULT 157 | fi 158 | exec $CLI 159 | else 160 | exit $RESULT 161 | fi 162 | -------------------------------------------------------------------------------- /acqstackdb/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for acqstackdb project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.8.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.8/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | import dj_database_url 16 | 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = '2liv5a*jjtx9qu)ewqz+1riim6!wgz1z@yebwh70*extgqaz5i' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = ( 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 'acquisitions', 42 | # 'acqstackdb', 43 | 44 | 'floppyforms', 45 | 'social.apps.django_app.default', 46 | 'smart_selects', 47 | 'ordered_model', 48 | ) 49 | 50 | MIDDLEWARE_CLASSES = ( 51 | 'django.middleware.security.SecurityMiddleware', 52 | 53 | 'whitenoise.middleware.WhiteNoiseMiddleware', 54 | 'django.contrib.sessions.middleware.SessionMiddleware', 55 | 'django.middleware.common.CommonMiddleware', 56 | 'django.middleware.csrf.CsrfViewMiddleware', 57 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 58 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 59 | 'django.contrib.messages.middleware.MessageMiddleware', 60 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 61 | ) 62 | 63 | ROOT_URLCONF = 'acqstackdb.urls' 64 | 65 | TEMPLATES = [ 66 | { 67 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 68 | 'DIRS': [], 69 | 'APP_DIRS': True, 70 | 'OPTIONS': { 71 | 'context_processors': [ 72 | 'django.template.context_processors.debug', 73 | 'django.template.context_processors.request', 74 | 'django.contrib.auth.context_processors.auth', 75 | 'django.contrib.messages.context_processors.messages', 76 | 'social.apps.django_app.context_processors.backends', 77 | 'social.apps.django_app.context_processors.login_redirect', 78 | ], 79 | }, 80 | }, 81 | ] 82 | 83 | WSGI_APPLICATION = 'acqstackdb.wsgi.application' 84 | 85 | 86 | # Database 87 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 88 | 89 | DATABASE_URL = os.environ.get("DATABASE_URL") 90 | DATABASES = {'default': dj_database_url.config( 91 | default="postgres://localhost/acqstackdb" 92 | )} 93 | 94 | 95 | # Internationalization 96 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 97 | 98 | LANGUAGE_CODE = 'en-us' 99 | 100 | TIME_ZONE = 'UTC' 101 | 102 | USE_I18N = True 103 | 104 | USE_L10N = True 105 | 106 | USE_TZ = True 107 | 108 | 109 | # Static files (CSS, JavaScript, Images) 110 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 111 | 112 | STATIC_URL = '/static/' 113 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles/') 114 | STATICFILES_DIRS = [ 115 | os.path.join(BASE_DIR, "third-party/uswds/"), 116 | os.path.join(BASE_DIR, "third-party/jquery/"), 117 | ] 118 | 119 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 120 | 121 | 122 | # Python Social Auth 123 | # https://github.com/omab/python-social-auth 124 | 125 | SOCIAL_AUTH_GITHUB_TEAM_KEY = os.environ.get("SOCIAL_AUTH_GITHUB_TEAM_KEY") 126 | SOCIAL_AUTH_GITHUB_TEAM_SECRET = os.environ.get( 127 | "SOCIAL_AUTH_GITHUB_TEAM_SECRET" 128 | ) 129 | SOCIAL_AUTH_GITHUB_TEAM_ID = os.environ.get("SOCIAL_AUTH_GITHUB_TEAM_ID") 130 | SOCIAL_AUTH_GITHUB_TEAM_SCOPE = ['read:org'] 131 | 132 | SOCIAL_AUTH_REDIRECT_IS_HTTPS = os.environ.get( 133 | "SOCIAL_AUTH_REDIRECT_IS_HTTPS", 134 | default="True" 135 | ) 136 | # os.environ returns a string rather than a boolean 137 | if str.lower(SOCIAL_AUTH_REDIRECT_IS_HTTPS) == "false": 138 | SOCIAL_AUTH_REDIRECT_IS_HTTPS = False 139 | else: 140 | SOCIAL_AUTH_REDIRECT_IS_HTTPS = True 141 | 142 | AUTHENTICATION_BACKENDS = ( 143 | 'social.backends.github.GithubTeamOAuth2', 144 | ) 145 | -------------------------------------------------------------------------------- /acquisitions/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from django.http import HttpResponse 3 | from django.contrib.auth import logout 4 | from django.contrib.auth.decorators import login_required 5 | from .models import Acquisition, Step, Stage, Track, Actor 6 | from acquisitions import forms 7 | from collections import OrderedDict 8 | 9 | 10 | # Create your views here. 11 | def home(request): 12 | acquisitions = Acquisition.objects.all() 13 | tracks = Track.objects.all() 14 | stages = Stage.objects.all() 15 | steps = Step.objects.all() 16 | data = OrderedDict() 17 | data["Overall"] = OrderedDict() 18 | actors = Actor.objects.all() 19 | 20 | for track in tracks: 21 | data[track.name] = OrderedDict() 22 | for stage in stages.all().order_by('order'): 23 | data[track.name][stage.name] = { 24 | "wip_limit": stage.wip_limit, 25 | "acquisitions": acquisitions.filter( 26 | step__stage=stage, track=track 27 | ), 28 | "steps": OrderedDict() 29 | } 30 | for step in steps.filter( 31 | stage=stage, track=track 32 | ).order_by('steptrackthroughmodel__order'): 33 | s = step.steptrackthroughmodel_set.filter(track=track).first() 34 | data[track.name][stage.name]["steps"][s.step.actor.name] = { 35 | "wip_limit": s.wip_limit, 36 | "acquisitions": acquisitions.filter(step=step, track=track) 37 | } 38 | 39 | for stage in stages.all().order_by('order'): 40 | data["Overall"][stage.name] = { 41 | "acquisitions": acquisitions.filter(step__stage=stage), 42 | "steps": OrderedDict() 43 | } 44 | for step in steps.filter( 45 | stage=stage 46 | ).order_by('steptrackthroughmodel__order'): 47 | data["Overall"][stage.name]["steps"][step.actor.name] = { 48 | "wip_limit": 0, 49 | "acquisitions": acquisitions.filter(step=step) 50 | } 51 | 52 | return render(request, "acquisitions/index.html", { 53 | "data": data, 54 | "actors": actors 55 | }) 56 | 57 | 58 | @login_required 59 | def acquisition(request, id): 60 | acquisition = get_object_or_404(Acquisition.objects.filter(id=id)) 61 | return render(request, 'acquisitions/acquisition.html', { 62 | 'acquisition': acquisition, 63 | 'statuses': Step.objects.all() 64 | }) 65 | 66 | 67 | @login_required 68 | def edit_acquisition(request, id): 69 | instance = Acquisition.objects.get(id=id) 70 | form = forms.AcquisitionForm(request.POST or None, instance=instance) 71 | if form.is_valid(): 72 | report = form.save() 73 | return redirect(acquisition, id=id) 74 | return render(request, "acquisitions/new.html", { 75 | 'form': form, 76 | 'item': 'Edit acquisition', 77 | 'action': '/acquisition/'+id+'/edit' 78 | }) 79 | 80 | 81 | @login_required 82 | def stages(request): 83 | form = forms.HiddenStageForm(request.POST or None) 84 | all_stages = Stage.objects.all() 85 | if form.is_valid(): 86 | stage = Stage.objects.get(name=request.POST.__getitem__('name')) 87 | if request.POST.__contains__('up'): 88 | stage.up() 89 | elif request.POST.__contains__('down'): 90 | stage.down() 91 | return redirect(stages) 92 | return render(request, 'acquisitions/stages.html', { 93 | 'form': form, 94 | 'stages': all_stages 95 | }) 96 | 97 | 98 | @login_required 99 | def new_index(request): 100 | return render(request, 'acquisitions/new_index.html') 101 | 102 | 103 | @login_required 104 | def new(request, item): 105 | forms_dict = { 106 | "acquisition": forms.AcquisitionForm(request.POST or None), 107 | "agency": forms.AgencyForm(request.POST or None), 108 | "subagency": forms.SubagencyForm(request.POST or None), 109 | "stage": forms.StageForm(request.POST or None), 110 | "step": forms.StepForm(request.POST or None), 111 | "track": forms.TrackForm(request.POST or None), 112 | "actor": forms.ActorForm(request.POST or None) 113 | } 114 | try: 115 | form = forms_dict[item] 116 | except: 117 | return render(request, "404.html") 118 | if form.is_valid(): 119 | report = form.save() 120 | return redirect(home) 121 | return render(request, "acquisitions/new.html", { 122 | 'form': form, 123 | 'item': 'New '+item, 124 | 'action': '/new/'+item 125 | }) 126 | 127 | 128 | def logout_view(request): 129 | logout(request) 130 | return redirect('home') 131 | -------------------------------------------------------------------------------- /acquisitions/migrations/0022_auto_20160726_2026.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-07-26 20:26 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0021_auto_20160712_2135'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='agency', 17 | options={'ordering': ('name',), 'verbose_name_plural': 'Agencies'}, 18 | ), 19 | migrations.AlterModelOptions( 20 | name='step', 21 | options={'ordering': ('steptrackthroughmodel__order',)}, 22 | ), 23 | migrations.AlterField( 24 | model_name='acquisition', 25 | name='competition_strategy', 26 | field=models.CharField(blank=True, choices=[('A/E Procedures', 'A/E Procedures'), ('Competed under SAP', 'Competed under SAP'), ('Competitive Delivery Order Fair Opportunity Provided', 'Competitive Delivery Order Fair Opportunity Provided'), ('Competitive Schedule Buy', 'Competitive Schedule Buy'), ('Fair Opportunity', 'Fair Opportunity'), ('Follow On to Competed Action (FAR 6.302-1)', 'Follow On to Competed Action (FAR 6.302-1)'), ('Follow On to Competed Action', 'Follow On to Competed Action'), ('Full and Open after exclusion of sources (competitive small business set-asides, competitive 8a)', 'Full and Open after exclusion of sources (competitive small business set-asides, competitive 8a)'), ('Full and Open Competition Unrestricted', 'Full and Open Competition Unrestricted'), ('Full and Open Competition', 'Full and Open Competition'), ('Limited Sources FSS Order', 'Limited Sources FSS Order'), ('Limited Sources', 'Limited Sources'), ('Non-Competitive Delivery Order', 'Non-Competitive Delivery Order'), ('Not Available for Competition (e.g., 8a sole source, HUBZone & SDVOSB sole source, Ability One, all > SAT)', 'Not Available for Competition (e.g., 8a sole source, HUBZone & SDVOSB sole source, Ability One, all > SAT)'), ('Not Competed (e.g., sole source, urgency, etc., all > SAT)', 'Not Competed (e.g., sole source, urgency, etc., all > SAT)'), ('Not Competed under SAP (e.g., Urgent, Sole source, Logical Follow-On, 8a, HUBZone & SDVOSB sole source, all < SAT)', 'Not Competed under SAP (e.g., Urgent, Sole source, Logical Follow-On, 8a, HUBZone & SDVOSB sole source, all < SAT)'), ('Partial Small Business Set-Aside', 'Partial Small Business Set-Aside'), ('Set-Aside', 'Set-Aside'), ('Sole Source', 'Sole Source')], max_length=100, null=True), 27 | ), 28 | migrations.AlterField( 29 | model_name='acquisition', 30 | name='contract_type', 31 | field=models.CharField(blank=True, choices=[('Cost No Fee', 'Cost No Fee'), ('Cost Plus Award Fee', 'Cost Plus Award Fee'), ('Cost Plus Fixed Fee', 'Cost Plus Fixed Fee'), ('Cost Plus Incentive Fee', 'Cost Plus Incentive Fee'), ('Cost Sharing', 'Cost Sharing'), ('Fixed Price Award Fee', 'Fixed Price Award Fee'), ('Fixed Price Incentive', 'Fixed Price Incentive'), ('Fixed Price Labor Hours', 'Fixed Price Labor Hours'), ('Fixed Price Level of Effort', 'Fixed Price Level of Effort'), ('Fixed Price Time and Materials', 'Fixed Price Time and Materials'), ('Fixed Price with Economic Price Adjustment', 'Fixed Price with Economic Price Adjustment'), ('Fixed Price', 'Fixed Price'), ('Interagency Agreement', 'Interagency Agreement'), ('Labor Hours and Time and Materials', 'Labor Hours and Time and Materials'), ('Labor Hours', 'Labor Hours'), ('Order Dependent', 'Order Dependent'), ('Time and Materials', 'Time and Materials')], max_length=100, null=True), 32 | ), 33 | migrations.AlterField( 34 | model_name='acquisition', 35 | name='procurement_method', 36 | field=models.CharField(blank=True, choices=[('Ability One', 'Ability One'), ('Basic Ordering Agreement', 'Basic Ordering Agreement'), ('Blanket Purchase Agreement-BPA', 'Blanket Purchase Agreement-BPA'), ('BPA Call', 'BPA Call'), ('Call Order under GSA Schedules BPA', 'Call Order under GSA Schedules BPA'), ('Commercial Item Contract', 'Commercial Item Contract'), ('Contract modification', 'Contract modification'), ('Contract', 'Contract'), ('Definitive Contract other than IDV', 'Definitive Contract other than IDV'), ('Definitive Contract', 'Definitive Contract'), ('Government-wide Agency Contract-GWAC', 'Government-wide Agency Contract-GWAC'), ('GSA Schedule Contract', 'GSA Schedule Contract'), ('GSA Schedule', 'GSA Schedule'), ('GSA Schedules Program BPA', 'GSA Schedules Program BPA'), ('Indefinite Delivery Indefinite Quantity-IDIQ', 'Indefinite Delivery Indefinite Quantity-IDIQ'), ('Indefinite Delivery Vehicle (IDV)', 'Indefinite Delivery Vehicle (IDV)'), ('Indefinite Delivery Vehicle Base Contract', 'Indefinite Delivery Vehicle Base Contract'), ('Multi-Agency Contract', 'Multi-Agency Contract'), ('Negotiated', 'Negotiated'), ('Order under GSA Federal Supply Schedules Program', 'Order under GSA Federal Supply Schedules Program'), ('Order under GSA Schedules Program BPA', 'Order under GSA Schedules Program BPA'), ('Order under GSA Schedules Program', 'Order under GSA Schedules Program'), ('Order under IDV', 'Order under IDV'), ('Purchase Order', 'Purchase Order'), ('Sealed Bid', 'Sealed Bid')], max_length=100, null=True), 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /acquisitions/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-27 21:34 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Acquisition', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('product_owner', models.CharField(max_length=50)), 22 | ('task', models.CharField(max_length=100)), 23 | ('rfq_id', models.IntegerField()), 24 | ('period_of_performance', models.DateField()), 25 | ('dollars', models.IntegerField()), 26 | ('set_aside_status', models.CharField(max_length=100)), 27 | ('amount_of_competition', models.IntegerField()), 28 | ('contract_type', models.CharField(max_length=100)), 29 | ('description', models.TextField(max_length=500)), 30 | ('naics', models.IntegerField()), 31 | ('competition_strategy', models.CharField(max_length=100)), 32 | ('procurement_method', models.CharField(max_length=100)), 33 | ('award_date', models.DateField()), 34 | ('delivery_date', models.DateField()), 35 | ], 36 | options={ 37 | 'ordering': ('rfq_id',), 38 | }, 39 | ), 40 | migrations.CreateModel( 41 | name='Agency', 42 | fields=[ 43 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 44 | ('name', models.CharField(max_length=100)), 45 | ], 46 | ), 47 | migrations.CreateModel( 48 | name='ContractingOffice', 49 | fields=[ 50 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 51 | ('name', models.CharField(max_length=100)), 52 | ], 53 | ), 54 | migrations.CreateModel( 55 | name='ContractingOfficer', 56 | fields=[ 57 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 58 | ('name', models.CharField(max_length=100)), 59 | ('contracting_office', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.ContractingOffice')), 60 | ], 61 | options={ 62 | 'ordering': ('name',), 63 | }, 64 | ), 65 | migrations.CreateModel( 66 | name='COR', 67 | fields=[ 68 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 69 | ('name', models.CharField(max_length=100)), 70 | ], 71 | ), 72 | migrations.CreateModel( 73 | name='Evaluator', 74 | fields=[ 75 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 76 | ('name', models.CharField(max_length=100)), 77 | ('acquisition', models.ManyToManyField(to='acquisitions.Acquisition')), 78 | ], 79 | options={ 80 | 'ordering': ('name',), 81 | }, 82 | ), 83 | migrations.CreateModel( 84 | name='Releases', 85 | fields=[ 86 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 87 | ('acquisition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Acquisition')), 88 | ], 89 | options={ 90 | 'ordering': ('id',), 91 | }, 92 | ), 93 | migrations.CreateModel( 94 | name='Subagency', 95 | fields=[ 96 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 97 | ('name', models.CharField(max_length=100)), 98 | ('agency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Agency')), 99 | ], 100 | options={ 101 | 'ordering': ('name',), 102 | }, 103 | ), 104 | migrations.AddField( 105 | model_name='acquisition', 106 | name='agency', 107 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Agency'), 108 | ), 109 | migrations.AddField( 110 | model_name='acquisition', 111 | name='contracting_office', 112 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.ContractingOffice'), 113 | ), 114 | migrations.AddField( 115 | model_name='acquisition', 116 | name='contracting_officer', 117 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.ContractingOfficer'), 118 | ), 119 | migrations.AddField( 120 | model_name='acquisition', 121 | name='contracting_officer_representative', 122 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.COR'), 123 | ), 124 | migrations.AddField( 125 | model_name='acquisition', 126 | name='subagency', 127 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='acquisitions.Subagency'), 128 | ), 129 | ] 130 | -------------------------------------------------------------------------------- /acquisitions/migrations/0003_auto_20160531_1702_squashed_0007_auto_20160531_2006.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-31 20:30 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | replaces = [('acquisitions', '0003_auto_20160531_1702'), ('acquisitions', '0004_acquisition_award_status'), ('acquisitions', '0005_auto_20160531_1952'), ('acquisitions', '0006_auto_20160531_1956'), ('acquisitions', '0007_auto_20160531_2006')] 12 | 13 | dependencies = [ 14 | ('acquisitions', '0002_auto_20160527_2144'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='acquisition', 20 | name='amount_of_competition', 21 | field=models.IntegerField(blank=True, null=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='acquisition', 25 | name='award_date', 26 | field=models.DateField(blank=True, null=True), 27 | ), 28 | migrations.AlterField( 29 | model_name='acquisition', 30 | name='competition_strategy', 31 | field=models.CharField(blank=True, max_length=100, null=True), 32 | ), 33 | migrations.AlterField( 34 | model_name='acquisition', 35 | name='contract_type', 36 | field=models.CharField(blank=True, max_length=100, null=True), 37 | ), 38 | migrations.AlterField( 39 | model_name='acquisition', 40 | name='contracting_office', 41 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.ContractingOffice'), 42 | ), 43 | migrations.AlterField( 44 | model_name='acquisition', 45 | name='contracting_officer', 46 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.ContractingOfficer'), 47 | ), 48 | migrations.AlterField( 49 | model_name='acquisition', 50 | name='contracting_officer_representative', 51 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='acquisitions.COR'), 52 | ), 53 | migrations.AlterField( 54 | model_name='acquisition', 55 | name='delivery_date', 56 | field=models.DateField(blank=True, null=True), 57 | ), 58 | migrations.AlterField( 59 | model_name='acquisition', 60 | name='description', 61 | field=models.TextField(blank=True, max_length=500, null=True), 62 | ), 63 | migrations.AlterField( 64 | model_name='acquisition', 65 | name='dollars', 66 | field=models.IntegerField(blank=True, null=True), 67 | ), 68 | migrations.AlterField( 69 | model_name='acquisition', 70 | name='naics', 71 | field=models.IntegerField(blank=True, null=True), 72 | ), 73 | migrations.AlterField( 74 | model_name='acquisition', 75 | name='period_of_performance', 76 | field=models.DateField(blank=True, null=True), 77 | ), 78 | migrations.AlterField( 79 | model_name='acquisition', 80 | name='procurement_method', 81 | field=models.CharField(blank=True, max_length=100, null=True), 82 | ), 83 | migrations.AlterField( 84 | model_name='acquisition', 85 | name='product_owner', 86 | field=models.CharField(blank=True, max_length=50, null=True), 87 | ), 88 | migrations.AlterField( 89 | model_name='acquisition', 90 | name='rfq_id', 91 | field=models.IntegerField(blank=True, null=True), 92 | ), 93 | migrations.AlterField( 94 | model_name='acquisition', 95 | name='set_aside_status', 96 | field=models.CharField(blank=True, max_length=100, null=True), 97 | ), 98 | migrations.AddField( 99 | model_name='acquisition', 100 | name='award_status', 101 | field=models.CharField(choices=[('1) 18F - Qual', '1) 18F - Qual'), ('2) OGC - Qual', '2) OGC - Qual'), ('3) OGP - Qual', '3) OGP - Qual'), ('4) 18F - Agreement Scoping', '4) 18F - Agreement Scoping'), ('5) 18F - Agreement Approval', '5) 18F - Agreement Approval'), ('6) OGC - Agreement Approval', '6) OGC - Agreement Approval'), ('7) 18F - RFQ Scoping', '7) 18F - RFQ Scoping'), ('8) OGC - RFQ Scoping', '8) OGC - RFQ Scoping'), ('9) OGP - RFQ Scoping', '9) OGP - RFQ Scoping'), ('10) 18F - RFQ Ready', '10) 18F - RFQ Ready'), ('11) 18F - RFQ on Street', '11) 18F - RFQ on Street'), ('12) 18F - Eval', '12) 18F - Eval'), ('13) OGC - Eval', '13) OGC - Eval'), ('14) OGP - Eval', '14) OGP - Eval'), ('15) 18F - Award', '15) 18F - Award'), ('16) OGC - Award', '16) OGC - Award'), ('17) OGP - Award', '17) OGP - Award'), ('18) 18F - Post-award', '18) 18F - Post-award')], default='18F - Qual', max_length=100), 102 | ), 103 | migrations.AlterModelOptions( 104 | name='acquisition', 105 | options={}, 106 | ), 107 | migrations.AlterField( 108 | model_name='acquisition', 109 | name='award_status', 110 | field=models.IntegerField(choices=[(1, '18F - Qual'), (2, 'OGC - Qual'), (3, 'OGP - Qual'), (4, '18F - Agreement Scoping'), (5, '18F - Agreement Approval'), (6, 'OGC - Agreement Approval'), (7, '18F - RFQ Scoping'), (8, 'OGC - RFQ Scoping'), (9, 'OGP - RFQ Scoping'), (10, ' 18F - RFQ Ready'), (11, ' 18F - RFQ on Street'), (12, ' 18F - Eval'), (13, ' OGC - Eval'), (14, ' OGP - Eval'), (15, ' 18F - Award'), (16, ' OGC - Award'), (17, ' OGP - Award'), (18, ' 18F - Post-award')], default=0), 111 | ), 112 | ] 113 | -------------------------------------------------------------------------------- /acquisitions/migrations/0007_auto_20160617_0357.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-17 03:57 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('acquisitions', '0006_auto_20160609_1416'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='acquisition', 17 | name='competition_strategy', 18 | field=models.CharField(blank=True, choices=[('Sole Source', 'Sole Source'), ('Full and Open', 'Full and Open'), ('Set-Aside', 'Set-Aside'), ('Partial Small Business Set-Aside', 'Partial Small Business Set-Aside'), ('A/E Procedures', 'A/E Procedures'), ('Full and Open Competition', 'Full and Open Competition'), ('Not Available for Competition (e.g., 8a sole source, HUBZone & SDVOSB sole source, Ability One, all > SAT)', 'Not Available for Competition (e.g., 8a sole source, HUBZone & SDVOSB sole source, Ability One, all > SAT)'), ('Not Competed (e.g., sole source, urgency, etc., all > SAT)', 'Not Competed (e.g., sole source, urgency, etc., all > SAT)'), ('Full and Open after exclusion of sources (competitive small business set-asides, competitive 8a)', 'Full and Open after exclusion of sources (competitive small business set-asides, competitive 8a)'), ('Follow On to Competed Action', 'Follow On to Competed Action'), ('Competed under SAP', 'Competed under SAP'), ('Not Competed under SAP (e.g., Urgent, Sole source, Logical Follow-On, 8a, HUBZone & SDVOSB sole source, all < SAT)', 'Not Competed under SAP (e.g., Urgent, Sole source, Logical Follow-On, 8a, HUBZone & SDVOSB sole source, all < SAT)'), ('Competitive Delivery Order Fair Opportunity Provided', 'Competitive Delivery Order Fair Opportunity Provided'), ('Non-Competitive Delivery Order', 'Non-Competitive Delivery Order'), ('Fair Opportunity', 'Fair Opportunity'), ('Sole-Source', 'Sole-Source'), ('Limited Sources', 'Limited Sources'), ('Competitive Schedule Buy', 'Competitive Schedule Buy'), ('Full and Open after exclusion of sources (competitive small business set-asides, competitive 8a)', 'Full and Open after exclusion of sources (competitive small business set-asides, competitive 8a)'), ('Full and Open Competition Unrestricted', 'Full and Open Competition Unrestricted'), ('Not Available for Competition (e.g., 8a sole source, HUBZone & SDVOSB sole source, Ability One, all > SAT)', 'Not Available for Competition (e.g., 8a sole source, HUBZone & SDVOSB sole source, Ability One, all > SAT)'), ('Not Competed (e.g., sole source, urgency, etc., all > SAT)', 'Not Competed (e.g., sole source, urgency, etc., all > SAT)'), ('Not Competed under SAP (e.g., Urgent, Sole source, Logical Follow-On, 8a, HUBZone & SDVOSB sole source, all < SAT)', 'Not Competed under SAP (e.g., Urgent, Sole source, Logical Follow-On, 8a, HUBZone & SDVOSB sole source, all < SAT)'), ('A/E Procedures', 'A/E Procedures'), ('Competed under SAP', 'Competed under SAP'), ('Follow On to Competed Action (FAR 6.302-1)', 'Follow On to Competed Action (FAR 6.302-1)'), ('Limited Sources FSS Order', 'Limited Sources FSS Order'), ('Competitive Schedule Buy', 'Competitive Schedule Buy'), ('Partial Small Business Set-Aside', 'Partial Small Business Set-Aside')], max_length=100, null=True), 19 | ), 20 | migrations.AlterField( 21 | model_name='acquisition', 22 | name='contract_type', 23 | field=models.CharField(blank=True, choices=[('Cost No Fee', 'Cost No Fee'), ('Cost Plus Award Fee', 'Cost Plus Award Fee'), ('Cost Plus Fixed Fee', 'Cost Plus Fixed Fee'), ('Cost Plus Incentive Fee', 'Cost Plus Incentive Fee'), ('Cost Sharing', 'Cost Sharing'), ('Fixed Price', 'Fixed Price'), ('Fixed Price Award Fee', 'Fixed Price Award Fee'), ('Fixed Price Incentive', 'Fixed Price Incentive'), ('Fixed Price Labor Hours', 'Fixed Price Labor Hours'), ('Fixed Price Level of Effort', 'Fixed Price Level of Effort'), ('Fixed Price Time and Materials', 'Fixed Price Time and Materials'), ('Fixed Price with Economic Price Adjustment', 'Fixed Price with Economic Price Adjustment'), ('Interagency Agreement', 'Interagency Agreement'), ('Labor Hours', 'Labor Hours'), ('Labor Hours and Time and Materials', 'Labor Hours and Time and Materials'), ('Order Dependent', 'Order Dependent'), ('Time and Materials', 'Time and Materials')], max_length=100, null=True), 24 | ), 25 | migrations.AlterField( 26 | model_name='acquisition', 27 | name='procurement_method', 28 | field=models.CharField(blank=True, choices=[('GSA Schedule', 'GSA Schedule'), ('Government-wide Agency Contract-GWAC', 'Government-wide Agency Contract-GWAC'), ('Basic Ordering Agreement', 'Basic Ordering Agreement'), ('Blanket Purchase Agreement-BPA', 'Blanket Purchase Agreement-BPA'), ('Multi-Agency Contract', 'Multi-Agency Contract'), ('BPA Call', 'BPA Call'), ('Purchase Order', 'Purchase Order'), ('Definitive Contract', 'Definitive Contract'), ('Ability One', 'Ability One'), ('Indefinite Delivery Indefinite Quantity-IDIQ', 'Indefinite Delivery Indefinite Quantity-IDIQ'), ('Negotiated', 'Negotiated'), ('Sealed Bid', 'Sealed Bid'), ('Contract', 'Contract'), ('Commercial Item Contract', 'Commercial Item Contract'), ('GSA Schedules Program BPA', 'GSA Schedules Program BPA'), ('Indefinite Delivery Vehicle (IDV)', 'Indefinite Delivery Vehicle (IDV)'), ('Purchase Order', 'Purchase Order'), ('Order under IDV', 'Order under IDV'), ('Order under GSA Schedules Program', 'Order under GSA Schedules Program'), ('Order under GSA Schedules Program BPA', 'Order under GSA Schedules Program BPA'), ('Definitive Contract other than IDV', 'Definitive Contract other than IDV'), ('Indefinite Delivery Vehicle Base Contract', 'Indefinite Delivery Vehicle Base Contract'), ('Order under GSA Federal Supply Schedules Program', 'Order under GSA Federal Supply Schedules Program'), ('Order under IDV', 'Order under IDV'), ('Purchase Order', 'Purchase Order'), ('Contract modification', 'Contract modification'), ('Ability One', 'Ability One'), ('Call Order under GSA Schedules BPA', 'Call Order under GSA Schedules BPA'), ('GSA Schedule Contract', 'GSA Schedule Contract'), ('Negotiated', 'Negotiated'), ('Sealed Bid', 'Sealed Bid'), ('Government-wide Agency Contract-GWAC', 'Government-wide Agency Contract-GWAC'), ('Commercial Item Contract', 'Commercial Item Contract'), ('GSA Schedules Program BPA', 'GSA Schedules Program BPA'), ('Basic Ordering Agreement', 'Basic Ordering Agreement'), ('Blanket Purchase Agreement-BPA', 'Blanket Purchase Agreement-BPA'), ('Multi-Agency Contract', 'Multi-Agency Contract')], max_length=100, null=True), 29 | ), 30 | migrations.AlterField( 31 | model_name='acquisition', 32 | name='set_aside_status', 33 | field=models.CharField(blank=True, choices=[('AbilityOne', 'AbilityOne'), ('HUBZone Small Business', 'HUBZone Small Business'), ('Multiple Small Business Categories', 'Multiple Small Business Categories'), ('Other Than Small', 'Other Than Small'), ('Service Disabled Veteran-owned Small Business', 'Service Disabled Veteran-owned Small Business'), ('Small Business', 'Small Business'), ('Small Disadvantaged Business (includes Section 8a)', 'Small Disadvantaged Business (includes Section 8a)'), ('To Be Determined-BPA', 'To Be Determined-BPA'), ('To Be Determined-IDIQ', 'To Be Determined-IDIQ'), ('Veteran-Owned Small Business', 'Veteran-Owned Small Business'), ('Woman-Owned Small Business', 'Woman-Owned Small Business')], max_length=100, null=True), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /acquisitions/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.core.validators import RegexValidator, ValidationError 3 | from django.utils.translation import ugettext_lazy as _ 4 | from django.contrib.auth.models import User 5 | from smart_selects.db_fields import ChainedForeignKey, ChainedManyToManyField 6 | from ordered_model.models import OrderedModel 7 | 8 | 9 | # Create your models here. 10 | class Agency(models.Model): 11 | name = models.CharField(max_length=100, blank=False) 12 | abbreviation = models.CharField(max_length=10, null=True, blank=True) 13 | department = models.CharField(max_length=100, null=True, blank=True) 14 | omb_agency_code = models.IntegerField(null=True, blank=True) 15 | omb_bureau_code = models.IntegerField(null=True, blank=True) 16 | treasury_agency_code = models.IntegerField(null=True, blank=True) 17 | cgac_agency_code = models.IntegerField(null=True, blank=True) 18 | 19 | def __str__(self): 20 | return self.name 21 | 22 | class Meta: 23 | verbose_name_plural = "Agencies" 24 | ordering = ('name',) 25 | 26 | 27 | class Subagency(models.Model): 28 | name = models.CharField(max_length=100, blank=False) 29 | abbreviation = models.CharField(max_length=10, null=True, blank=True) 30 | agency = models.ForeignKey(Agency) 31 | 32 | def __str__(self): 33 | return "%s - %s" % (self.agency, self.name) 34 | 35 | class Meta: 36 | ordering = ('name',) 37 | verbose_name_plural = "Subagencies" 38 | 39 | 40 | class ContractingOffice(models.Model): 41 | name = models.CharField(max_length=100) 42 | 43 | def __str__(self): 44 | return self.name 45 | 46 | class Meta: 47 | verbose_name = "Contracting Office" 48 | verbose_name_plural = "Contracting Offices" 49 | 50 | 51 | class ContractingOfficer(models.Model): 52 | name = models.CharField(max_length=100) 53 | contracting_office = models.ForeignKey(ContractingOffice) 54 | 55 | def __str__(self): 56 | return "%s - %s" % (self.name, self.contracting_office) 57 | 58 | class Meta: 59 | ordering = ('name',) 60 | verbose_name = "Contracting Officer" 61 | verbose_name_plural = "Contracting Officers" 62 | 63 | 64 | class COR(models.Model): 65 | name = models.CharField(max_length=100) 66 | 67 | def __str__(self): 68 | return self.name 69 | 70 | class Meta: 71 | ordering = ('name',) 72 | verbose_name = "Contracting Officer Representative" 73 | verbose_name_plural = "Contracting Officer Representatives" 74 | 75 | 76 | # Is the acquisition internal or external? 77 | class Track(models.Model): 78 | name = models.CharField(max_length=50) 79 | 80 | def __str__(self): 81 | return "%s" % (self.name) 82 | 83 | 84 | class Stage(OrderedModel): 85 | name = models.CharField(max_length=50) 86 | wip_limit = models.IntegerField(default=0, verbose_name="WIP Limit") 87 | 88 | def __str__(self): 89 | return "%s" % (self.name) 90 | 91 | class Meta(OrderedModel.Meta): 92 | pass 93 | 94 | 95 | class Actor(models.Model): 96 | name = models.CharField(max_length=200, blank=False) 97 | 98 | def __str__(self): 99 | return "%s" % (self.name) 100 | 101 | 102 | class Step(models.Model): 103 | actor = models.ForeignKey( 104 | Actor, 105 | blank=False 106 | ) 107 | track = models.ManyToManyField( 108 | Track, 109 | blank=False, 110 | through="StepTrackThroughModel" 111 | ) 112 | stage = models.ForeignKey( 113 | Stage, 114 | blank=False 115 | ) 116 | 117 | def __str__(self): 118 | return "%s - %s" % (self.stage, self.actor,) 119 | 120 | class Meta: 121 | ordering = ('steptrackthroughmodel__order',) 122 | 123 | 124 | class StepTrackThroughModel(OrderedModel): 125 | track = models.ForeignKey(Track) 126 | step = models.ForeignKey(Step) 127 | wip_limit = models.IntegerField(default=0, verbose_name="WIP Limit") 128 | order_with_respect_to = 'track' 129 | 130 | class Meta(OrderedModel.Meta): 131 | unique_together = ('track', 'step') 132 | ordering = ('track', 'order') 133 | 134 | 135 | class Vendor(models.Model): 136 | name = models.CharField(max_length=200, blank=False) 137 | email = models.EmailField(blank=False) 138 | duns = models.CharField(max_length=9, blank=False, validators=[ 139 | RegexValidator(regex='^\d{9}$', message="DUNS number must be 9 digits") 140 | ]) 141 | 142 | def __str__(self): 143 | return self.name 144 | 145 | 146 | class Role(models.Model): 147 | description = models.CharField(max_length=100, choices=( 148 | ('P', 'Product Lead'), 149 | ('A', 'Acquisition Lead'), 150 | ('T', 'Technical Lead') 151 | ), null=True, blank=True) 152 | teammate = models.ForeignKey(User, blank=True, null=True) 153 | 154 | def __str__(self): 155 | return "%s - %s" % (self.get_description_display(), self.teammate) 156 | 157 | 158 | class Acquisition(models.Model): 159 | SET_ASIDE_CHOICES = ( 160 | ("AbilityOne", "AbilityOne"), 161 | ("HUBZone Small Business", "HUBZone Small Business"), 162 | ("Multiple Small Business Categories", 163 | "Multiple Small Business Categories"), 164 | ("Other Than Small", "Other Than Small"), 165 | ("Service Disabled Veteran-owned Small Business", 166 | "Service Disabled Veteran-owned Small Business"), 167 | ("Small Business", "Small Business"), 168 | ("Small Disadvantaged Business (includes Section 8a)", 169 | "Small Disadvantaged Business (includes Section 8a)"), 170 | ("To Be Determined-BPA", "To Be Determined-BPA"), 171 | ("To Be Determined-IDIQ", "To Be Determined-IDIQ"), 172 | ("Veteran-Owned Small Business", "Veteran-Owned Small Business"), 173 | ("Woman-Owned Small Business", "Woman-Owned Small Business"), 174 | ) 175 | 176 | CONTRACT_TYPE_CHOICES = ( 177 | ("Cost No Fee", "Cost No Fee"), 178 | ("Cost Plus Award Fee", "Cost Plus Award Fee"), 179 | ("Cost Plus Fixed Fee", "Cost Plus Fixed Fee"), 180 | ("Cost Plus Incentive Fee", "Cost Plus Incentive Fee"), 181 | ("Cost Sharing", "Cost Sharing"), 182 | ("Fixed Price Award Fee", "Fixed Price Award Fee"), 183 | ("Fixed Price Incentive", "Fixed Price Incentive"), 184 | ("Fixed Price Labor Hours", "Fixed Price Labor Hours"), 185 | ("Fixed Price Level of Effort", "Fixed Price Level of Effort"), 186 | ("Fixed Price Time and Materials", "Fixed Price Time and Materials"), 187 | ("Fixed Price with Economic Price Adjustment", 188 | "Fixed Price with Economic Price Adjustment"), 189 | ("Fixed Price", "Fixed Price"), 190 | ("Interagency Agreement", "Interagency Agreement"), 191 | ("Labor Hours and Time and Materials", 192 | "Labor Hours and Time and Materials"), 193 | ("Labor Hours", "Labor Hours"), 194 | ("Order Dependent", "Order Dependent"), 195 | ("Time and Materials", "Time and Materials"), 196 | ) 197 | 198 | COMPETITION_STRATEGY_CHOICES = ( 199 | ("A/E Procedures", "A/E Procedures"), 200 | ("Competed under SAP", "Competed under SAP"), 201 | ("Competitive Delivery Order Fair Opportunity Provided", 202 | "Competitive Delivery Order Fair Opportunity Provided"), 203 | ("Competitive Schedule Buy", "Competitive Schedule Buy"), 204 | ("Fair Opportunity", "Fair Opportunity"), 205 | ("Follow On to Competed Action (FAR 6.302-1)", 206 | "Follow On to Competed Action (FAR 6.302-1)"), 207 | ("Follow On to Competed Action", "Follow On to Competed Action"), 208 | ("Full and Open after exclusion of sources (competitive small business \ 209 | set-asides, competitive 8a)", 210 | "Full and Open after exclusion of sources (competitive small \ 211 | business set-asides, competitive 8a)"), 212 | ("Full and Open Competition Unrestricted", 213 | "Full and Open Competition Unrestricted"), 214 | ("Full and Open Competition", "Full and Open Competition"), 215 | ("Limited Sources FSS Order", "Limited Sources FSS Order"), 216 | ("Limited Sources", "Limited Sources"), 217 | ("Non-Competitive Delivery Order", "Non-Competitive Delivery Order"), 218 | ("Not Available for Competition (e.g., 8a sole source, HUBZone & \ 219 | SDVOSB sole source, Ability One, all > SAT)", 220 | "Not Available for Competition (e.g., 8a sole source, HUBZone & \ 221 | SDVOSB sole source, Ability One, all > SAT)"), 222 | ("Not Competed (e.g., sole source, urgency, etc., all > SAT)", 223 | "Not Competed (e.g., sole source, urgency, etc., all > SAT)"), 224 | ("Not Competed under SAP (e.g., Urgent, Sole source, Logical \ 225 | Follow-On, 8a, HUBZone & SDVOSB sole source, all < SAT)", 226 | "Not Competed under SAP (e.g., Urgent, Sole source, Logical \ 227 | Follow-On, 8a, HUBZone & SDVOSB sole source, all < SAT)"), 228 | ("Partial Small Business Set-Aside", 229 | "Partial Small Business Set-Aside"), 230 | ("Set-Aside", "Set-Aside"), 231 | ("Sole Source", "Sole Source"), 232 | ) 233 | 234 | PROCUREMENT_METHOD_CHOICES = ( 235 | ("Ability One", "Ability One"), 236 | ("Basic Ordering Agreement", "Basic Ordering Agreement"), 237 | ("Blanket Purchase Agreement-BPA", "Blanket Purchase Agreement-BPA"), 238 | ("BPA Call", "BPA Call"), 239 | ("Call Order under GSA Schedules BPA", 240 | "Call Order under GSA Schedules BPA"), 241 | ("Commercial Item Contract", "Commercial Item Contract"), 242 | ("Contract modification", "Contract modification"), 243 | ("Contract", "Contract"), 244 | ("Definitive Contract other than IDV", 245 | "Definitive Contract other than IDV"), 246 | ("Definitive Contract", "Definitive Contract"), 247 | ("Government-wide Agency Contract-GWAC", 248 | "Government-wide Agency Contract-GWAC"), 249 | ("GSA Schedule Contract", "GSA Schedule Contract"), 250 | ("GSA Schedule", "GSA Schedule"), 251 | ("GSA Schedules Program BPA", "GSA Schedules Program BPA"), 252 | ("Indefinite Delivery Indefinite Quantity-IDIQ", 253 | "Indefinite Delivery Indefinite Quantity-IDIQ"), 254 | ("Indefinite Delivery Vehicle (IDV)", 255 | "Indefinite Delivery Vehicle (IDV)"), 256 | ("Indefinite Delivery Vehicle Base Contract", 257 | "Indefinite Delivery Vehicle Base Contract"), 258 | ("Multi-Agency Contract", "Multi-Agency Contract"), 259 | ("Negotiated", "Negotiated"), 260 | ("Order under GSA Federal Supply Schedules Program", 261 | "Order under GSA Federal Supply Schedules Program"), 262 | ("Order under GSA Schedules Program BPA", 263 | "Order under GSA Schedules Program BPA"), 264 | ("Order under GSA Schedules Program", 265 | "Order under GSA Schedules Program"), 266 | ("Order under IDV", "Order under IDV"), 267 | ("Purchase Order", "Purchase Order"), 268 | ("Sealed Bid", "Sealed Bid"), 269 | ) 270 | 271 | subagency = models.ForeignKey(Subagency) 272 | task = models.CharField(max_length=100, blank=False) 273 | description = models.TextField(max_length=500, null=True, blank=True) 274 | track = models.ForeignKey( 275 | Track, 276 | blank=False, 277 | related_name="%(class)s_track" 278 | ) 279 | step = ChainedForeignKey( 280 | Step, 281 | chained_field="track", 282 | chained_model_field="track", 283 | blank=False 284 | ) 285 | dollars = models.DecimalField(decimal_places=2, max_digits=14, null=True, 286 | blank=True) 287 | period_of_performance = models.DateField(null=True, blank=True) 288 | product_owner = models.CharField(max_length=50, null=True, blank=True) 289 | roles = models.ManyToManyField(Role, blank=True) 290 | contracting_officer = models.ForeignKey(ContractingOfficer, null=True, 291 | blank=True) 292 | contracting_officer_representative = models.ForeignKey(COR, null=True, 293 | blank=True) 294 | contracting_office = models.ForeignKey(ContractingOffice, null=True, 295 | blank=True) 296 | vendor = models.ForeignKey(Vendor, null=True, blank=True) 297 | rfq_id = models.IntegerField(null=True, blank=True, verbose_name="RFQ ID") 298 | naics = models.IntegerField( 299 | null=True, 300 | blank=True, 301 | verbose_name="NAICS Code" 302 | ) 303 | set_aside_status = models.CharField(max_length=100, null=True, blank=True, 304 | choices=SET_ASIDE_CHOICES) 305 | amount_of_competition = models.IntegerField(null=True, blank=True) 306 | contract_type = models.CharField(max_length=100, null=True, blank=True, 307 | choices=CONTRACT_TYPE_CHOICES) 308 | competition_strategy = models.CharField( 309 | max_length=100, 310 | null=True, 311 | blank=True, 312 | choices=COMPETITION_STRATEGY_CHOICES) 313 | procurement_method = models.CharField( 314 | max_length=100, 315 | null=True, 316 | blank=True, 317 | choices=PROCUREMENT_METHOD_CHOICES) 318 | award_date = models.DateField(null=True, blank=True) 319 | delivery_date = models.DateField(null=True, blank=True) 320 | 321 | def clean(self): 322 | print(self.step.track.all()) 323 | print(self.track) 324 | if self.track not in self.step.track.all(): 325 | raise ValidationError(_('Tracks are not equal.')) 326 | 327 | def __str__(self): 328 | return "%s (%s)" % (self.task, self.subagency) 329 | 330 | 331 | class Evaluator(models.Model): 332 | name = models.CharField(max_length=100) 333 | acquisition = models.ManyToManyField(Acquisition) 334 | 335 | def __str__(self): 336 | return self.name 337 | 338 | class Meta: 339 | ordering = ('name',) 340 | 341 | 342 | class Release(models.Model): 343 | acquisition = models.ForeignKey(Acquisition) 344 | 345 | def __str__(self): 346 | return self.id 347 | 348 | class Meta: 349 | ordering = ('id',) 350 | --------------------------------------------------------------------------------