├── .circleci └── config.yml ├── .codeclimate.yml ├── .coveragerc ├── .gitignore ├── .python-version ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── Procfile ├── README.md ├── access_audit ├── __init__.py ├── admin.py ├── helpers.py ├── middleware.py └── tests │ ├── __init__.py │ └── test_middleware.py ├── app.json ├── bin └── post_compile ├── clips ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_clip_title.py │ ├── 0003_clip_deleted.py │ └── __init__.py ├── models.py ├── templates │ └── clips │ │ ├── clip_form.html │ │ └── clip_update_form.html ├── tests │ ├── __init__.py │ ├── factories │ │ └── clip_factory.py │ └── test_clips.py ├── urls.py └── views.py ├── email_csv ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── favicons ├── favicon.svg ├── management │ └── commands │ │ └── build_favicons.py └── static │ └── favicons │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── browserconfig.xml │ ├── icon-16x16.png │ ├── icon-192x192.png │ ├── icon-32x32.png │ ├── icon-96x96.png │ ├── manifest.json │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ └── ms-icon-70x70.png ├── features ├── __init__.py ├── app_index_views.feature ├── contact_partnerships.feature ├── easy_audit.feature ├── edit_application.feature ├── edit_applications_with_bad_data.feature ├── environment.py ├── search_applications.feature ├── search_followups.feature ├── stats.feature ├── steps │ ├── __init__.py │ ├── applications_steps.py │ ├── audit_steps.py │ ├── county_applications_steps.py │ ├── debugging_hacks.py │ ├── followups_steps.py │ ├── form_input_steps.py │ ├── language_hacks.py │ ├── notification_steps.py │ ├── pdf_steps.py │ ├── user_accounts_steps.py │ └── web_loads_and_contains_steps.py └── unread_notifications.feature ├── formation ├── __init__.py ├── base.py ├── combinable_base.py ├── display_form_base.py ├── exceptions.py ├── field_base.py ├── field_types.py ├── fields.py ├── form_base.py ├── forms.py ├── render_base.py ├── templates │ └── formation │ │ ├── address_display.jinja │ │ ├── checkbox_select.jinja │ │ ├── counties_display.jinja │ │ ├── counties_input.jinja │ │ ├── default_form_display.jinja │ │ ├── default_input_display.jinja │ │ ├── email_display.jinja │ │ ├── example.html │ │ ├── generic_form.jinja │ │ ├── intake_display.jinja │ │ ├── messages_list.jinja │ │ ├── multivalue_address.jinja │ │ ├── multivalue_field.jinja │ │ ├── option_set_display.jinja │ │ ├── phone_display.jinja │ │ ├── radio_select.jinja │ │ ├── text_input.jinja │ │ ├── textarea_field.jinja │ │ └── url_display.jinja ├── tests │ ├── __init__.py │ ├── mock.py │ ├── rendered_samples.py │ ├── sample_answers.py │ ├── test_base.py │ ├── test_combinable_base.py │ ├── test_display_form_base.py │ ├── test_field_base.py │ ├── test_field_types.py │ ├── test_fields.py │ ├── test_form_base.py │ ├── test_render_base.py │ ├── test_validators.py │ └── utils.py └── validators.py ├── health_check ├── __init__.py ├── tests │ ├── __init__.py │ └── test_urls.py └── urls.py ├── helper_commands ├── __init__.py ├── apps.py └── management │ └── commands │ ├── download_data.py │ ├── export_csv.py │ ├── restore_deleted_profiles.py │ ├── run_debug_task.py │ ├── send_front_notifications_from_logs.py │ ├── upload_data.py │ └── utils.py ├── intake ├── __init__.py ├── admin.py ├── anonymous_names.py ├── apps.py ├── constants.py ├── example_contexts.py ├── exceptions.py ├── fixtures │ ├── 0015_add_default_counties_data.json │ ├── counties.json │ └── template_options.json ├── forms │ ├── __init__.py │ ├── application_transfer_form.py │ └── status_update_forms.py ├── groups.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── backfill_applicants.py │ │ ├── backfill_create_crudevents.py │ │ ├── find_all_duplicates.py │ │ ├── heroku_release.py │ │ ├── load_2000_mock_submissions.py │ │ ├── load_essential_data.py │ │ ├── load_mock_data.py │ │ ├── migrate_dob.py │ │ ├── new_fixtures.py │ │ ├── prepare_review_env.py │ │ ├── send_followups.py │ │ ├── send_unopened_apps_notification.py │ │ ├── test_fillpdf.py │ │ └── test_urls_200.py ├── middleware.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_fillablepdf.py │ ├── 0003_fillablepdf_name.py │ ├── 0004_auto_20160603_2355.py │ ├── 0005_auto_20160606_1949.py │ ├── 0006_auto_20160614_2216.py │ ├── 0007_auto_20160615_1954.py │ ├── 0008_auto_20160615_2307.py │ ├── 0009_auto_20160615_2323.py │ ├── 0010_auto_20160706_2000.py │ ├── 0011_confirmationsentlogentry_message_sent.py │ ├── 0012_auto_20160706_2010.py │ ├── 0013_add_county_model.py │ ├── 0014_county_name.py │ ├── 0015_add_default_counties.py │ ├── 0016_applicationlogentry_organization.py │ ├── 0017_fillablepdf_organization.py │ ├── 0018_add_default_org_to_pdfs_and_logs.py │ ├── 0019_update_contact_preferences_answer_data.py │ ├── 0020_formsubmission_organizations.py │ ├── 0021_tie_orgs_to_submissions.py │ ├── 0022_filledpdf.py │ ├── 0023_applicationbundle.py │ ├── 0024_app_bundle_pdf_not_required.py │ ├── 0025_remove_formsubmission_counties.py │ ├── 0026_applicationlogentry_add_transfer_type.py │ ├── 0027_applicant_applicationevent.py │ ├── 0028_formsubmission_applicant.py │ ├── 0029_applicationevent_order_by_time.py │ ├── 0030_add_visitor_model.py │ ├── 0031_add_view_app_stats_permission.py │ ├── 0032_add_view_app_details_permission.py │ ├── 0033_visitor_ip_address.py │ ├── 0034_duplicate_submission_set.py │ ├── 0035_applicationnote.py │ ├── 0036_add_view_app_notes_permission.py │ ├── 0037_tags.py │ ├── 0038_create_next_step_and_status_types.py │ ├── 0039_create_status_update_without_application.py │ ├── 0040_create_status_notification.py │ ├── 0041_enforce_unique_slugs.py │ ├── 0042_fix_permissions_and_non_db_model_updates.py │ ├── 0043_status_update_related_name.py │ ├── 0044_one_to_one_mapping_between_status_update_and_notification.py │ ├── 0045_add_is_active_to_template_options.py │ ├── 0046_add_other_next_steps_to_status_updates.py │ ├── 0047_graduate_fields_from_answers.py │ ├── 0048_template_field_validators.py │ ├── 0049_template_option_is_a_status_update_choice.py │ ├── 0050_application_created_updated.py │ ├── 0051_backfill_updated_created_on_applications.py │ ├── 0052_application_created_auto.py │ ├── 0053_applicationtransfer.py │ ├── 0054_graduate_additional_fields.py │ ├── 0055_visitor_uuid.py │ ├── 0056_backfill_visitors_for_applicants.py │ ├── 0057_applicant_visitor_onetoone.py │ ├── 0058_replace_event_logic_with_flags.py │ ├── 0059_prebuiltpdfbundle.py │ ├── 0060_visitor_locale_user_agent.py │ ├── 0061_purgedformsubmission.py │ ├── 0062_add_id_purgedformsubmission.py │ ├── 0063_purgedapplication_purgedstatusupdate.py │ ├── 0064_purgedvisitor.py │ ├── 0065_purgedapplicant.py │ ├── 0066_purgedapplicationnote.py │ ├── 0067_purgedcounty_purgednextstep_purgedstatusnotification_purgedstatustype_purgedsubmissiontaglink_purged.py │ ├── 0068_purgedformsubmission_add_applicant_id.py │ ├── 0069_purgedapplication_add_created_and_updated.py │ ├── 0070_formsubmission_unlisted_counties.py │ ├── 0071_add_display_order_to_template_option.py │ ├── 0072_auto_20200115_1801.py │ └── __init__.py ├── models │ ├── __init__.py │ ├── abstract_base_models.py │ ├── applicant.py │ ├── application.py │ ├── application_bundle.py │ ├── application_event.py │ ├── application_log_entry.py │ ├── application_transfer.py │ ├── county.py │ ├── fields.py │ ├── form_submission.py │ ├── next_step.py │ ├── note.py │ ├── pdfs.py │ ├── prebuilt_pdf_bundle.py │ ├── status_notification.py │ ├── status_type.py │ ├── status_update.py │ ├── tag.py │ ├── template_option.py │ └── visitor.py ├── notifications.py ├── pdfparser.jar ├── pdfparser.py ├── permissions.py ├── serializers │ ├── __init__.py │ ├── app_index_serializers.py │ ├── applicant_serializer.py │ ├── application_serializers.py │ ├── application_transfer_serializer.py │ ├── fields.py │ ├── form_submission_serializer.py │ ├── note_serializer.py │ ├── organization_serializer.py │ ├── request_serializer.py │ ├── shortcuts.py │ ├── status_update_serializer.py │ ├── tag_serializer.py │ └── view_serializer.py ├── service_objects │ ├── __init__.py │ └── applicant_notifications.py ├── services │ ├── __init__.py │ ├── applicants.py │ ├── applications_service.py │ ├── bundles.py │ ├── display_form_service.py │ ├── edit_form_service.py │ ├── events_service.py │ ├── followups.py │ ├── mailgun_api_service.py │ ├── messages_service.py │ ├── pagination.py │ ├── pdf_service.py │ ├── search_service.py │ ├── statistics.py │ ├── status_notifications.py │ ├── submissions.py │ ├── tags.py │ └── transfers_service.py ├── static │ ├── intake │ │ ├── css │ │ │ ├── cfa.css │ │ │ └── cfa.css.map │ │ ├── fonts │ │ ├── icomoon │ │ │ ├── icomoon.eot │ │ │ ├── icomoon.svg │ │ │ ├── icomoon.ttf │ │ │ └── icomoon.woff │ │ ├── img │ │ │ ├── 10_minutes.png │ │ │ ├── 10_minutes@2x.png │ │ │ ├── CMR-hero.png │ │ │ ├── CMR_share.png │ │ │ ├── CMR_social_thumbnail.png │ │ │ ├── alameda_seal.png │ │ │ ├── alameda_seal@2x.png │ │ │ ├── ben-golder.jpg │ │ │ ├── bundle-icon.png │ │ │ ├── bundle-icon@2x.png │ │ │ ├── ca.png │ │ │ ├── ca@2x.png │ │ │ ├── cfa-logo-black.png │ │ │ ├── cfa-logo-black@2x.png │ │ │ ├── cfa_logo.svg │ │ │ ├── cfa_logo@2x.png │ │ │ ├── cfa_logo_color.png │ │ │ ├── cfa_logo_color@2x.png │ │ │ ├── cfa_logo_white.png │ │ │ ├── cmr_logo_black_cropped.png │ │ │ ├── cmr_logo_black_cropped@2x.png │ │ │ ├── contra_costa_seal.png │ │ │ ├── contra_costa_seal@2x.png │ │ │ ├── eligibility-icon.png │ │ │ ├── eligibility-icon@2x.png │ │ │ ├── fresno_seal.png │ │ │ ├── fresno_seal@2x.png │ │ │ ├── geometry.png │ │ │ ├── jazmyn-latimer.jpg │ │ │ ├── job_search.png │ │ │ ├── job_search@2x.png │ │ │ ├── ladders.png │ │ │ ├── ladders@2x.png │ │ │ ├── notification-icon.png │ │ │ ├── notification-icon@2x.png │ │ │ ├── official_doc.png │ │ │ ├── official_doc@2x.png │ │ │ ├── ok_hand.png │ │ │ ├── ok_hand@2x.png │ │ │ ├── partner-interface.png │ │ │ ├── partner-interface@2x.png │ │ │ ├── pencil.png │ │ │ ├── pencil@2x.png │ │ │ ├── san_diego_seal.png │ │ │ ├── san_diego_seal@2x.png │ │ │ ├── san_francisco_seal.png │ │ │ ├── san_francisco_seal@2x.png │ │ │ ├── santa_barbara_seal.png │ │ │ ├── santa_barbara_seal@2x.png │ │ │ ├── santa_clara_seal.png │ │ │ ├── santa_clara_seal@2x.png │ │ │ ├── santa_cruz_seal.png │ │ │ ├── santa_cruz_seal@2x.png │ │ │ ├── solano_seal.png │ │ │ ├── solano_seal@2x.png │ │ │ ├── speech_bubbles.png │ │ │ ├── speech_bubbles@2x.png │ │ │ ├── tiffany-andrews.jpg │ │ │ ├── ventura_seal.png │ │ │ └── ventura_seal@2x.png │ │ ├── js │ │ │ ├── admin_entry.js │ │ │ ├── ajax.js │ │ │ ├── application.js │ │ │ ├── chart_configs.js │ │ │ ├── chart_size_measures.js │ │ │ ├── csrf.js │ │ │ ├── d3.js │ │ │ ├── org_charts.js │ │ │ ├── search_widget.js │ │ │ ├── stats_entry.js │ │ │ ├── tag_search.js │ │ │ ├── tag_widget.js │ │ │ ├── templates.js │ │ │ ├── utils.js │ │ │ ├── x_axis.js │ │ │ └── y_axis.js │ │ ├── less │ │ │ ├── admin.less │ │ │ ├── charts.less │ │ │ ├── clear.less │ │ │ ├── custom.less │ │ │ ├── flash_messages.less │ │ │ ├── form_display.less │ │ │ ├── forms.less │ │ │ ├── main.less │ │ │ ├── mixins.less │ │ │ ├── sunset-notice.less │ │ │ └── variables.less │ │ └── scss │ │ │ ├── _ie-hacks.scss │ │ │ ├── _normalize.scss │ │ │ ├── _shame.scss │ │ │ ├── atoms │ │ │ ├── _base.scss │ │ │ ├── _buttons.scss │ │ │ ├── _emoji.scss │ │ │ ├── _form-elements.scss │ │ │ ├── _form-widths.scss │ │ │ ├── _grid.scss │ │ │ ├── _icons.scss │ │ │ ├── _illustrations.scss │ │ │ ├── _images.scss │ │ │ ├── _labels.scss │ │ │ ├── _layout.scss │ │ │ ├── _typography.scss │ │ │ ├── _utilities.scss │ │ │ └── _variables.scss │ │ │ ├── bourbon │ │ │ ├── _bourbon.scss │ │ │ └── bourbon │ │ │ │ ├── helpers │ │ │ │ ├── _buttons-list.scss │ │ │ │ ├── _scales.scss │ │ │ │ └── _text-inputs-list.scss │ │ │ │ ├── library │ │ │ │ ├── _border-color.scss │ │ │ │ ├── _border-radius.scss │ │ │ │ ├── _border-style.scss │ │ │ │ ├── _border-width.scss │ │ │ │ ├── _buttons.scss │ │ │ │ ├── _clearfix.scss │ │ │ │ ├── _contrast-switch.scss │ │ │ │ ├── _ellipsis.scss │ │ │ │ ├── _font-face.scss │ │ │ │ ├── _font-stacks.scss │ │ │ │ ├── _hide-text.scss │ │ │ │ ├── _hide-visually.scss │ │ │ │ ├── _margin.scss │ │ │ │ ├── _modular-scale.scss │ │ │ │ ├── _overflow-wrap.scss │ │ │ │ ├── _padding.scss │ │ │ │ ├── _position.scss │ │ │ │ ├── _prefixer.scss │ │ │ │ ├── _shade.scss │ │ │ │ ├── _size.scss │ │ │ │ ├── _strip-unit.scss │ │ │ │ ├── _text-inputs.scss │ │ │ │ ├── _timing-functions.scss │ │ │ │ ├── _tint.scss │ │ │ │ ├── _triangle.scss │ │ │ │ └── _value-prefixer.scss │ │ │ │ ├── settings │ │ │ │ └── _settings.scss │ │ │ │ ├── utilities │ │ │ │ ├── _assign-inputs.scss │ │ │ │ ├── _compact-shorthand.scss │ │ │ │ ├── _contrast-ratio.scss │ │ │ │ ├── _directional-property.scss │ │ │ │ ├── _fetch-bourbon-setting.scss │ │ │ │ ├── _font-source-declaration.scss │ │ │ │ ├── _gamma.scss │ │ │ │ ├── _lightness.scss │ │ │ │ └── _unpack-shorthand.scss │ │ │ │ └── validators │ │ │ │ ├── _contains-falsy.scss │ │ │ │ ├── _contains.scss │ │ │ │ ├── _is-color.scss │ │ │ │ ├── _is-length.scss │ │ │ │ ├── _is-number.scss │ │ │ │ └── _is-size.scss │ │ │ ├── cfa.scss │ │ │ ├── molecules │ │ │ ├── _data-table.scss │ │ │ ├── _flashes.scss │ │ │ ├── _form-molecules.scss │ │ │ ├── _incrementer.scss │ │ │ ├── _media-box.scss │ │ │ ├── _progress-indicator.scss │ │ │ ├── _scroller.scss │ │ │ ├── _searchbar.scss │ │ │ ├── _steps.scss │ │ │ ├── _summary-table.scss │ │ │ ├── _tabs.scss │ │ │ └── _toolbar.scss │ │ │ ├── neat │ │ │ ├── _neat-helpers.scss │ │ │ ├── _neat.scss │ │ │ ├── functions │ │ │ │ ├── _new-breakpoint.scss │ │ │ │ └── _private.scss │ │ │ ├── grid │ │ │ │ ├── _box-sizing.scss │ │ │ │ ├── _direction-context.scss │ │ │ │ ├── _display-context.scss │ │ │ │ ├── _fill-parent.scss │ │ │ │ ├── _media.scss │ │ │ │ ├── _omega.scss │ │ │ │ ├── _outer-container.scss │ │ │ │ ├── _pad.scss │ │ │ │ ├── _private.scss │ │ │ │ ├── _row.scss │ │ │ │ ├── _shift.scss │ │ │ │ ├── _span-columns.scss │ │ │ │ ├── _to-deprecate.scss │ │ │ │ └── _visual-grid.scss │ │ │ ├── mixins │ │ │ │ └── _clearfix.scss │ │ │ └── settings │ │ │ │ ├── _disable-warnings.scss │ │ │ │ ├── _grid.scss │ │ │ │ └── _visual-grid.scss │ │ │ ├── organisms │ │ │ ├── _county-seal-list.scss │ │ │ ├── _demo-banner.scss │ │ │ ├── _document-preview.scss │ │ │ ├── _form-card.scss │ │ │ ├── _legal.scss │ │ │ ├── _main-footer.scss │ │ │ ├── _main-header.scss │ │ │ ├── _pagination.scss │ │ │ ├── _statistic-card.scss │ │ │ └── _sunset-notice-alert.scss │ │ │ └── templates │ │ │ ├── _banner.scss │ │ │ └── _partnerships-contact.scss │ └── voicemail │ │ └── CMR_voicemail_greeting.mp3 ├── tasks.py ├── tests │ ├── __init__.py │ ├── base_testcases.py │ ├── factories │ │ ├── __init__.py │ │ ├── applicant_factory.py │ │ ├── county_factory.py │ │ ├── factory_shortcuts.py │ │ ├── fillable_pdf_factory.py │ │ ├── filled_pdf_factory.py │ │ ├── form_submission_factory.py │ │ ├── prebuilt_pdf_bundle_factory.py │ │ ├── status_notification_factory.py │ │ ├── status_type.py │ │ ├── status_update_factory.py │ │ ├── submission_tag_link_factory.py │ │ ├── tag_factory.py │ │ ├── utils.py │ │ └── visitor_factory.py │ ├── fake_answers_dictionary.py │ ├── forms │ │ ├── __init__.py │ │ └── test_status_update_forms.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── test_send_followups.py │ ├── mock.py │ ├── mock_county_forms │ │ └── __init__.py │ ├── mock_org_answers.py │ ├── mock_referrers.py │ ├── mock_serialized_apps.py │ ├── mock_user_agents.py │ ├── mock_utils.py │ ├── models │ │ ├── __init__.py │ │ ├── test_applicant.py │ │ ├── test_application.py │ │ ├── test_application_bundle.py │ │ ├── test_application_event.py │ │ ├── test_appliction_log_entry.py │ │ ├── test_county.py │ │ ├── test_form_submission.py │ │ ├── test_note.py │ │ ├── test_pdfs.py │ │ ├── test_prebuilt_pdf_bundle.py │ │ ├── test_status_notification.py │ │ ├── test_status_update.py │ │ ├── test_tag.py │ │ ├── test_template_options.py │ │ └── test_visitor.py │ ├── serializers │ │ ├── __init__.py │ │ ├── test_app_index_serializers.py │ │ ├── test_fields.py │ │ └── test_request_serializer.py │ ├── service_objects │ │ ├── __init__.py │ │ └── test_applicant_notifications.py │ ├── services │ │ ├── __init__.py │ │ ├── test_applicants.py │ │ ├── test_applications_service.py │ │ ├── test_bundles.py │ │ ├── test_edit_form_service.py │ │ ├── test_followups.py │ │ ├── test_mailgun_api_service.py │ │ ├── test_pagination.py │ │ ├── test_pdf_service.py │ │ ├── test_statistics.py │ │ ├── test_status_notifications.py │ │ ├── test_submissions.py │ │ ├── test_tags.py │ │ └── test_transfers_service.py │ ├── test_commands.py │ ├── test_fields.py │ ├── test_form_specs.py │ ├── test_groups.py │ ├── test_middleware.py │ ├── test_models.py │ ├── test_not_validating_too_much.py │ ├── test_notifications.py │ ├── test_translators.py │ ├── test_utils.py │ ├── test_validators.py │ ├── test_views.py │ ├── unit │ │ └── __init__.py │ └── views │ │ ├── __init__.py │ │ ├── test_admin_views.py │ │ ├── test_app_detail_views.py │ │ ├── test_app_edit_view.py │ │ ├── test_applicant_form_view_base.py │ │ ├── test_application_done_view.py │ │ ├── test_application_note_views.py │ │ ├── test_application_transfer_view.py │ │ ├── test_county_application_view.py │ │ ├── test_data_export_views.py │ │ ├── test_declaration_letter_view.py │ │ ├── test_newapps_pdf_view.py │ │ ├── test_prebuilt_pdf_bundle_views.py │ │ ├── test_printout_views.py │ │ ├── test_public_views.py │ │ ├── test_redirect_link_views.py │ │ ├── test_search_views.py │ │ ├── test_select_county_view.py │ │ ├── test_stats_views.py │ │ ├── test_status_update_views.py │ │ └── test_tag_views.py ├── translators │ ├── __init__.py │ ├── base.py │ ├── clean_slate.py │ └── fields.py ├── urls.py ├── utils.py ├── validators.py └── views │ ├── __init__.py │ ├── admin_views.py │ ├── app_detail_views.py │ ├── app_edit_view.py │ ├── applicant_form_view_base.py │ ├── application_done_view.py │ ├── application_note_views.py │ ├── application_transfer_view.py │ ├── base_views.py │ ├── county_application_view.py │ ├── data_export_views.py │ ├── declaration_letter_view.py │ ├── legacy_redirect_views.py │ ├── prebuilt_pdf_bundle_views.py │ ├── printout_views.py │ ├── public_views.py │ ├── redirect_link_views.py │ ├── search_views.py │ ├── select_county_view.py │ ├── stats_views.py │ ├── status_update_views.py │ └── tag_views.py ├── local_settings.py.example ├── maintenance.html ├── manage.py ├── newrelic.ini ├── package-lock.json ├── package.json ├── partnerships ├── __init__.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tests │ ├── __init__.py │ ├── factories.py │ ├── test_models.py │ └── test_views.py ├── urls.py └── views.py ├── phone ├── __init__.py ├── apps.py ├── tests │ ├── __init__.py │ ├── test_validators.py │ └── test_views.py ├── urls.py ├── validators.py └── views.py ├── printing ├── __init__.py └── pdf_form_display.py ├── project ├── __init__.py ├── alerts.py ├── celery.py ├── content.py ├── decorators.py ├── exceptions.py ├── fixtures_index.py ├── heroku_wsgi.py ├── jinja2.py ├── migration_utils.py ├── services │ ├── __init__.py │ ├── logging_service.py │ ├── mixpanel_service.py │ └── query_params.py ├── settings │ ├── __init__.py │ ├── base.py │ ├── circleci.py │ ├── deployed.py │ ├── development.py │ ├── environment.py │ ├── prod.py │ ├── review.py │ ├── staging.py │ └── test.py ├── templates │ ├── 400.html │ ├── 403.html │ ├── 404.html │ ├── 500.html │ └── robots.txt ├── tests │ ├── __init__.py │ ├── assertions.py │ ├── services │ │ ├── __init__.py │ │ └── test_logging_service.py │ ├── test_decorators.py │ └── utils.py ├── urls.py └── wsgi.py ├── requirements.txt ├── requirements ├── app.txt ├── ci.txt ├── dev.txt └── prod.txt ├── runtime.txt ├── setup.cfg ├── system.properties ├── templates ├── admin_base_single_column.jinja ├── admin_form_base.jinja ├── app_bundle.jinja ├── app_bundle_action_bar.jinja ├── app_detail.jinja ├── app_detail_action_bar.jinja ├── app_edit.jinja ├── app_history.jinja ├── app_index.jinja ├── app_reviewer_list.jinja ├── application_set.jinja ├── apply_page.jinja ├── base.jinja ├── cmr_base.jinja ├── create_status_update.jinja ├── email │ ├── app_bundle_email.jinja │ ├── applicant_edit_notification.jinja │ ├── confirmation.jinja │ ├── followup.jinja │ └── org_edit_notification.jinja ├── followup_list.jinja ├── followup_list_rows.jinja ├── forms │ ├── county_form.jinja │ ├── county_form_display.jinja │ ├── county_form_review.jinja │ ├── county_selection.jinja │ ├── declaration_letter_display.jinja │ ├── declaration_letter_form.jinja │ └── declaration_letter_review.jinja ├── gcf-style-flash-messages.jinja ├── gcf-style-footer.jinja ├── gcf-style-public-header.jinja ├── gcf_style_base.jinja ├── includes │ ├── app_detail_navigation.jinja │ ├── app_index_navigation.jinja │ ├── application_event.jinja │ ├── application_next_steps.jinja │ ├── auth_bar.jinja │ ├── back_to_home_button.jinja │ ├── cmr_contact_info.jinja │ ├── county-seals.jinja │ ├── county_list.jinja │ ├── csrf_field.jinja │ ├── empty_status_summary.jinja │ ├── flash_messages.jinja │ ├── followup_list_row.jinja │ ├── footer.jinja │ ├── google_analytics.jinja │ ├── head.jinja │ ├── header.jinja │ ├── latest_status_summary.jinja │ ├── latest_status_summary_for_incoming_transfer.jinja │ ├── latest_status_summary_for_outgoing_transfer.jinja │ ├── minimal_latest_status_line.jinja │ ├── mixpanel.jinja │ ├── org_user_app_index_listing.jinja │ ├── org_user_transferred_app_listing.jinja │ ├── public_flash_messages.jinja │ ├── public_topbar.jinja │ ├── results_paginator.jinja │ ├── social_meta.jinja │ └── submission_display_actions.jinja ├── macros.jinja ├── main_splash.jinja ├── newapps_pdf_detail.jinja ├── packet_instructions.jinja ├── partner_detail.jinja ├── partner_list.jinja ├── partnerships-contact.jinja ├── partnerships.jinja ├── personal_statement_instructions.jinja ├── privacy_policy.jinja ├── public_base.jinja ├── public_base_single_column.jinja ├── rap_sheet_instructions.jinja ├── review_status_notification.jinja ├── slack │ ├── app_bundle_sent.jinja │ ├── bundle_action.jinja │ ├── bundle_viewed.jinja │ ├── new_submission.jinja │ ├── notification_failed.jinja │ ├── notification_sent.jinja │ ├── submission_action.jinja │ ├── submission_deleted.jinja │ └── submission_viewed.jinja ├── stats.jinja ├── sunset_notice.jinja ├── text │ ├── applicant_edit_notification.jinja │ ├── confirmation.jinja │ └── followup.jinja ├── thanks.jinja └── transfer_application.jinja ├── tests ├── __init__.py ├── sample_pdfs │ ├── sample_form.pdf │ └── sample_form_filled.pdf └── sample_translator.py └── user_accounts ├── __init__.py ├── admin.py ├── base_views.py ├── exceptions.py ├── fixtures ├── 0007_add_default_orgs_for_counties_data.json ├── addresses.json ├── groups.json └── organizations.json ├── forms.py ├── management ├── __init__.py └── commands │ ├── __init__.py │ ├── alert_admins_if_org_has_unread_applications.py │ └── create_email_forwarding_for_users.py ├── migrations ├── 0001_initial.py ├── 0002_auto_20160524_1808.py ├── 0003_auto_20160525_1728.py ├── 0004_userprofile_should_get_notifications.py ├── 0005_organization_is_receiving_agency.py ├── 0006_add_county_model.py ├── 0007_add_default_orgs_for_counties.py ├── 0008_add_profiles_related_name_to_organization.py ├── 0009_organization_slug.py ├── 0010_add_slugs_to_orgs.py ├── 0011_organization_confirmation_message_contact_info_etc.py ├── 0012_organization_show_pdf_only.py ├── 0013_address.py ├── 0014_long_and_short_confirmation_messages.py ├── 0015_organization_notify_on_weekends.py ├── 0016_add_more_invitation_options.py ├── 0017_organization_followup_message_fields.py ├── 0018_org_accepting_apps_checking_notifications.py ├── 0019_order_orgs_by_name.py ├── 0020_organization_is_live.py ├── 0021_organization_transfer_partners.py ├── 0022_organization_needs_applicant_followups.py ├── 0023_organization_fax_number.py ├── 0024_userprofile_uuid.py ├── 0025_purgedorganization.py ├── 0026_remove_hayward_clean_slate_clinic_from_addresses.py ├── 0027_remove_oakland_clinic_from_addresses.py └── __init__.py ├── models ├── __init__.py ├── address.py ├── invitation.py ├── organization.py └── user_profile.py ├── serializers ├── __init__.py ├── shortcuts.py └── user_serializer.py ├── tasks.py ├── templates ├── account │ └── email │ │ ├── password_reset_key_message.txt │ │ └── password_reset_key_subject.txt ├── invitations │ └── email │ │ ├── email_invite_message.txt │ │ └── email_invite_subject.txt └── user_accounts │ ├── change_password.jinja │ ├── invite_form.jinja │ ├── login.jinja │ ├── password_reset_sent.jinja │ ├── request_password_reset.jinja │ ├── signup.jinja │ └── userprofile_form.jinja ├── tests ├── __init__.py ├── base_testcases.py ├── clients.py ├── factories │ ├── __init__.py │ ├── base_factory.py │ ├── organization_factory.py │ ├── user_factory.py │ └── user_profile_factory.py ├── management │ └── commands │ │ └── test_alert_if_org_is_absent.py ├── mock.py ├── models │ ├── __init__.py │ ├── test_address.py │ ├── test_organization.py │ └── test_user_profile.py ├── test_auth_integration.py └── test_views.py ├── urls.py └── views.py /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | radon: 3 | enabled: true 4 | duplication: 5 | enabled: true 6 | config: 7 | languages: 8 | - python 9 | fixme: 10 | enabled: true 11 | pep8: 12 | enabled: true 13 | ratings: 14 | paths: 15 | - "project/**.py" 16 | - "intake/**.py" 17 | - "user_accounts/**.py" 18 | exclude_paths: 19 | - "tests/**/*" 20 | - "**/tests/**/*.py" 21 | - "**/migrations/**/*" 22 | - "project/settings/*.py" -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = 3 | project 4 | intake 5 | user_accounts 6 | formation 7 | omit = 8 | */tests/* 9 | project/settings/* 10 | project/heroku_wsgi.py 11 | project/wsgi.py 12 | intake/translators/clean_slate.py 13 | */migrations/* 14 | intake/views/session_view_base.py 15 | intake/views/application_form_views.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src 2 | __pycache__ 3 | *.pyc 4 | *.swp 5 | zappa_settings.json 6 | .tower_cli.cfg 7 | 8 | # system file sometimes generated by OS X 9 | .DS_Store 10 | 11 | # virtualenv 12 | pyvenv.cfg 13 | pip-selfcheck.json 14 | .Python 15 | .venv/ 16 | 17 | # test outputs to ignore 18 | .coverage 19 | 20 | .env 21 | db.sqlite3 22 | node_modules 23 | staticfiles 24 | staticfiles-cache 25 | project/media 26 | media 27 | local_settings.py 28 | .sass-cache 29 | local.log 30 | notes/*.md 31 | selenium/ 32 | geckodriver.log 33 | 34 | # hiding fixtures 35 | intake/fixtures/mock* 36 | user_accounts/fixtures/mock_profiles.json 37 | 38 | # IDE settings 39 | .idea 40 | tags 41 | 42 | .env* -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.6.8 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.3 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby "2.6.3" 3 | gem "sass" 4 | gem "neat", "1.8.0" 5 | gem "bourbon" 6 | gem "normalize-scss" 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | bourbon (4.3.4) 5 | sass (~> 3.4) 6 | thor (~> 0.19) 7 | neat (1.8.0) 8 | sass (>= 3.3) 9 | thor (~> 0.19) 10 | normalize-scss (6.0.0) 11 | sass (~> 3.3) 12 | sass (3.4.23) 13 | thor (0.19.4) 14 | 15 | PLATFORMS 16 | ruby 17 | 18 | DEPENDENCIES 19 | bourbon 20 | neat (= 1.8.0) 21 | normalize-scss 22 | sass 23 | 24 | RUBY VERSION 25 | ruby 2.6.3p62 26 | 27 | BUNDLED WITH 28 | 1.17.2 29 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: python manage.py heroku_release 2 | worker: NEW_RELIC_CONFIG_FILE=newrelic.ini newrelic-admin run-program celery worker --app=project.celery --concurrency=2 -E --loglevel=INFO --without-gossip --without-mingle --without-heartbeat 3 | web: NEW_RELIC_CONFIG_FILE=newrelic.ini newrelic-admin run-program gunicorn project.heroku_wsgi --log-file=- 4 | clock: celery beat --app=project.celery -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler -------------------------------------------------------------------------------- /access_audit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/access_audit/__init__.py -------------------------------------------------------------------------------- /access_audit/admin.py: -------------------------------------------------------------------------------- 1 | from easyaudit.models import CRUDEvent, LoginEvent 2 | from django.contrib import admin 3 | 4 | 5 | admin.site.unregister(CRUDEvent) 6 | admin.site.unregister(LoginEvent) 7 | -------------------------------------------------------------------------------- /access_audit/helpers.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def dont_audit_fixture_loading( 4 | instance, object_json_repr, created, raw, using, update_fields, 5 | **kwargs): 6 | return not raw 7 | -------------------------------------------------------------------------------- /access_audit/middleware.py: -------------------------------------------------------------------------------- 1 | from intake.middleware import MiddlewareBase 2 | from easyaudit.middleware.easyaudit import clear_request 3 | 4 | 5 | class ClearRequestMiddleware(MiddlewareBase): 6 | 7 | def process_response(self, response): 8 | clear_request() 9 | -------------------------------------------------------------------------------- /access_audit/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/access_audit/tests/__init__.py -------------------------------------------------------------------------------- /bin/post_compile: -------------------------------------------------------------------------------- 1 | python manage.py compress --engine jinja2 --extension jinja 2 | -------------------------------------------------------------------------------- /clips/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/clips/__init__.py -------------------------------------------------------------------------------- /clips/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /clips/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ClipsConfig(AppConfig): 5 | name = 'clips' 6 | -------------------------------------------------------------------------------- /clips/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-08-14 23:59 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Clip', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('query', models.TextField()), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /clips/migrations/0002_clip_title.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-08-16 20:53 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 | ('clips', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='clip', 17 | name='title', 18 | field=models.CharField(default='Placeholder', max_length=64), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /clips/migrations/0003_clip_deleted.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-06-25 22:34 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('clips', '0002_clip_title'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='clip', 15 | name='deleted', 16 | field=models.DateTimeField(editable=False, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /clips/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/clips/migrations/__init__.py -------------------------------------------------------------------------------- /clips/templates/clips/clip_form.html: -------------------------------------------------------------------------------- 1 |
{% csrf_token %} 2 | {{ form.as_p }} 3 | 4 |
5 | 6 | 11 | -------------------------------------------------------------------------------- /clips/templates/clips/clip_update_form.html: -------------------------------------------------------------------------------- 1 |
{% csrf_token %} 2 | {{ form.as_p }} 3 | 4 |
5 | 6 | 7 | {% for row in object.run %} 8 | {% for column in row %} 9 | 14 | {% endfor %} 15 | {% endfor %} 16 |
{% if '\n' in column %} 10 |
{{ column }}
11 | {% else %} 12 | {{ column }} 13 | {% endif %}
17 | 18 |
19 | {% csrf_token %} 20 | 21 |
22 | -------------------------------------------------------------------------------- /clips/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/clips/tests/__init__.py -------------------------------------------------------------------------------- /clips/tests/factories/clip_factory.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from clips import models 3 | 4 | 5 | class ClipFactory(factory.DjangoModelFactory): 6 | title = 'My awesome clip' 7 | query = 'select id from auth_user;' 8 | 9 | class Meta: 10 | model = models.Clip 11 | -------------------------------------------------------------------------------- /clips/tests/test_clips.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from clips.models import Clip, run_query 3 | from clips.tests.factories.clip_factory import ClipFactory 4 | 5 | 6 | class TestClips(TestCase): 7 | 8 | def test_run_query(self): 9 | """Test that it can run a query on the database 10 | without erroring 11 | """ 12 | 13 | result = run_query("select id from auth_user") 14 | self.assertEquals(result, [['id']]) 15 | 16 | def test_clip_all(self): 17 | """Test that get_all returns undeleted Clips by default""" 18 | clip_one = ClipFactory(title='Query one') 19 | clip_two = ClipFactory() 20 | clip_two.delete() 21 | 22 | self.assertEquals(len(Clip.objects.all()), 1) 23 | self.assertEquals(Clip.objects.all()[0], clip_one) 24 | -------------------------------------------------------------------------------- /clips/urls.py: -------------------------------------------------------------------------------- 1 | from django2_url_robots.utils import url 2 | from . import views 3 | 4 | urlpatterns = [ 5 | url(r'^$', views.ClipCreateView.as_view(), 6 | name='clips-create', robots_allow=False), 7 | url(r'^(?P[0-9]+)/$', views.ClipUpdateView.as_view(), 8 | name='clips-update', robots_allow=False), 9 | url(r'^(?P[0-9]+)/delete$', views.ClipDeleteView.as_view(), 10 | name='clips-delete', robots_allow=False), 11 | ] 12 | -------------------------------------------------------------------------------- /email_csv/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/email_csv/__init__.py -------------------------------------------------------------------------------- /email_csv/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /email_csv/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class EmailCsvConfig(AppConfig): 5 | name = 'email_csv' 6 | -------------------------------------------------------------------------------- /email_csv/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/email_csv/migrations/__init__.py -------------------------------------------------------------------------------- /email_csv/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /email_csv/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /email_csv/urls.py: -------------------------------------------------------------------------------- 1 | from django2_url_robots.utils import url 2 | from . import views 3 | 4 | urlpatterns = [ 5 | url(r'^$', views.email_csv, 6 | name='email-csv', robots_allow=False), 7 | 8 | ] 9 | -------------------------------------------------------------------------------- /favicons/static/favicons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/android-icon-144x144.png -------------------------------------------------------------------------------- /favicons/static/favicons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/android-icon-192x192.png -------------------------------------------------------------------------------- /favicons/static/favicons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/android-icon-36x36.png -------------------------------------------------------------------------------- /favicons/static/favicons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/android-icon-48x48.png -------------------------------------------------------------------------------- /favicons/static/favicons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/android-icon-72x72.png -------------------------------------------------------------------------------- /favicons/static/favicons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/android-icon-96x96.png -------------------------------------------------------------------------------- /favicons/static/favicons/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /favicons/static/favicons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /favicons/static/favicons/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /favicons/static/favicons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /favicons/static/favicons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /favicons/static/favicons/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /favicons/static/favicons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /favicons/static/favicons/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /favicons/static/favicons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /favicons/static/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /favicons/static/favicons/icon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/icon-16x16.png -------------------------------------------------------------------------------- /favicons/static/favicons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/icon-192x192.png -------------------------------------------------------------------------------- /favicons/static/favicons/icon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/icon-32x32.png -------------------------------------------------------------------------------- /favicons/static/favicons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/icon-96x96.png -------------------------------------------------------------------------------- /favicons/static/favicons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/ms-icon-144x144.png -------------------------------------------------------------------------------- /favicons/static/favicons/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/ms-icon-150x150.png -------------------------------------------------------------------------------- /favicons/static/favicons/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/ms-icon-310x310.png -------------------------------------------------------------------------------- /favicons/static/favicons/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/favicons/static/favicons/ms-icon-70x70.png -------------------------------------------------------------------------------- /features/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/features/__init__.py -------------------------------------------------------------------------------- /features/contact_partnerships.feature: -------------------------------------------------------------------------------- 1 | Feature: Visitor can submit a partnerships interest form 2 | Scenario: Visitor submits a minimal partnerships interest form 3 | Given that "/partnerships/get-in-touch" loads 4 | When the "name" text input is set to "Ziggy Stardust" 5 | And the "email" email input is set to "ziggy@mars.space" 6 | And the "organization_name" text input is set to "Spiders from Mars" 7 | And submit button in form "partnerships-contact-form" is clicked 8 | Then it should load "/partnerships/" 9 | And it should have the element ".flash.success" which says "Thanks for reaching out! Your message has been sent" -------------------------------------------------------------------------------- /features/easy_audit.feature: -------------------------------------------------------------------------------- 1 | Feature: CRUD events are audited 2 | Background: 3 | Given a superuser 4 | And an org user at "ebclc" 5 | 6 | Scenario: Admin actions create CRUD events 7 | Given I log in as a superuser 8 | When I go to the admin edit page for "ebclc" user 9 | And I check "is_staff" 10 | And I select the "followup_staff" option in "groups_old" 11 | And I click "a#id_groups_add_link" 12 | And I click "input[name='_save']" 13 | Then the latest "auth.User" "update" event should have "superuser" as the user 14 | And the latest "auth.User" "update" event should have "True" for "is_staff" 15 | And the latest "auth.User" "m2m_change" event should have "2" ids in "groups" 16 | -------------------------------------------------------------------------------- /features/search_applications.feature: -------------------------------------------------------------------------------- 1 | Feature: An org user can search for an application and navigate to its detail page 2 | As on org user, I want to quickly search for an application 3 | in order to check the case status 4 | 5 | Background: 6 | Given an org user at "ebclc" 7 | And "100" applications to "ebclc" 8 | And a "ebclc" application to search for 9 | 10 | Scenario: Org user can search for application and find details 11 | Given I log in as an org user at "ebclc" 12 | And that "/applications/" loads 13 | When I search for the applicant's name 14 | Then I should see the applicant's name in search results 15 | When I click on the applicant's search result 16 | Then it should load the applicant's detail page 17 | -------------------------------------------------------------------------------- /features/search_followups.feature: -------------------------------------------------------------------------------- 1 | Feature: A staff user can search for and modify submissions 2 | As CfA staff, I want to quickly search for an applicant 3 | in order to add a note and provide case support for applicants. 4 | 5 | Background: 6 | Given an applicant support user 7 | And "100" applications 8 | And an applicant to search for 9 | 10 | Scenario: CfA user can find applicant and add a note 11 | Given I log in as an applicant support user 12 | And that "/applications/" loads 13 | When I search for the applicant's name 14 | Then I should see the applicant's followup row 15 | And the create note form should be visible 16 | When I add a note about the applicant's case 17 | Then the note should be visible 18 | -------------------------------------------------------------------------------- /features/stats.feature: -------------------------------------------------------------------------------- 1 | Feature: StatsPage Loads 2 | Scenario: Has static assets 3 | Given that "/stats/" loads 4 | And it loads css 5 | -------------------------------------------------------------------------------- /features/steps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/features/steps/__init__.py -------------------------------------------------------------------------------- /features/steps/debugging_hacks.py: -------------------------------------------------------------------------------- 1 | from behave import then 2 | 3 | 4 | @then('I need to debug') 5 | def test_debugging(context): 6 | print("\n---------------------------------") 7 | print("HTML in the browser") 8 | print(context.browser.page_source) 9 | print("\n---------------------------------") 10 | assert False 11 | -------------------------------------------------------------------------------- /features/steps/language_hacks.py: -------------------------------------------------------------------------------- 1 | """A utility module turning English into machine-readable data.""" 2 | 3 | 4 | def oxford_comma_text_to_list(phrase): 5 | """Examples: 6 | - 'Eeeny, Meeny, Miney, and Moe' --> ['Eeeny', 'Meeny', 'Miney', 'Moe'] 7 | - 'Black and White' --> ['Black', 'White'] 8 | - 'San Francisco and Saint Francis' --> 9 | ['San Francisco', 'Saint Francisco'] 10 | """ 11 | items = [] 12 | for subphrase in phrase.split(', '): 13 | items.extend( 14 | [item.strip() for item in subphrase.split(' and ')]) 15 | return items 16 | -------------------------------------------------------------------------------- /formation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/formation/__init__.py -------------------------------------------------------------------------------- /formation/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class RawDataMustBeDictError(Exception): 4 | """Raise this if the input raw data for 5 | forms or form fields is not a dict or dict descendant 6 | (MultiValueDict, QueryDict) 7 | """ 8 | pass 9 | 10 | 11 | class NoChoicesGivenError(Exception): 12 | """Raise this when a field that operates on a set of 13 | choices is instantiated without any choices. 14 | """ 15 | pass 16 | 17 | 18 | class MultiValueFieldSubfieldError(NotImplementedError): 19 | """Raise this when someone tries to instantiate a 20 | MultiValueField without a defined `subfields` attribute 21 | """ 22 | pass 23 | 24 | 25 | class InvalidPhoneNumberException(Exception): 26 | """Raise this when an invalid phone number is found 27 | """ 28 | pass 29 | -------------------------------------------------------------------------------- /formation/templates/formation/address_display.jinja: -------------------------------------------------------------------------------- 1 |
2 |
{{ field.get_display_label() }}
3 | {%- if not field.is_empty() %} 4 |
5 |
{{ field.get_display_value() }}
6 | {%- else %} 7 |
8 | {%- endif %} 9 |
-------------------------------------------------------------------------------- /formation/templates/formation/counties_display.jinja: -------------------------------------------------------------------------------- 1 |
2 |
{{ field.get_display_label() }}
3 | {%- if not field.is_empty() %} 4 |
5 | {{ field.get_display_value(unlisted_counties=unlisted_counties) }} 6 |
7 | {%- else %} 8 |
9 | {%- endif %} 10 |
-------------------------------------------------------------------------------- /formation/templates/formation/default_form_display.jinja: -------------------------------------------------------------------------------- 1 | {%- for field in form.get_usable_fields() %} 2 | {{ field.display() }} 3 | {%- endfor %} -------------------------------------------------------------------------------- /formation/templates/formation/default_input_display.jinja: -------------------------------------------------------------------------------- 1 |
2 |
{{ field.get_display_label() }}
3 | {%- if not field.is_empty() %} 4 |
{{ field.get_display_value() }}
5 | {%- else %} 6 |
7 | {%- endif %} 8 |
-------------------------------------------------------------------------------- /formation/templates/formation/email_display.jinja: -------------------------------------------------------------------------------- 1 |
2 |
{{ field.get_display_label() }}
3 | {%- if not field.is_empty() %} 4 | 9 | {%- else %} 10 |
11 | {%- endif %} 12 |
-------------------------------------------------------------------------------- /formation/templates/formation/example.html: -------------------------------------------------------------------------------- 1 | {{ message }} -------------------------------------------------------------------------------- /formation/templates/formation/generic_form.jinja: -------------------------------------------------------------------------------- 1 | 2 | {% for field in form.iter_fields() %} 3 | {{ field.render()|indent(4)|safe }} 4 | {% endfor %} 5 | -------------------------------------------------------------------------------- /formation/templates/formation/messages_list.jinja: -------------------------------------------------------------------------------- 1 | {%- if field.errors -%} 2 | 9 | {%- endif -%} 10 | 11 | {%- if field.warnings -%} 12 | 19 | {%- endif -%} -------------------------------------------------------------------------------- /formation/templates/formation/option_set_display.jinja: -------------------------------------------------------------------------------- 1 |
2 |
{{ field.get_display_label() }}
3 | {%- if not field.is_empty() %} 4 |
5 | {%- for option, option_display in field.get_display_choices() %} 6 | 10 | {{ field.get_display_for_choice(option) }} 11 | 12 | {%- endfor %} 13 |
14 | {%- else %} 15 |
16 | {%- endif %} 17 |
-------------------------------------------------------------------------------- /formation/templates/formation/phone_display.jinja: -------------------------------------------------------------------------------- 1 |
2 |
{{ field.get_display_label() }}
3 | {%- set display_value = field.get_display_value() %} 4 | {%- if display_value %} 5 |
6 | {%- if field.get_current_value_parsed() %} 7 | 8 | {{- display_value -}} 9 | 10 | {%- else %} 11 | {{- display_value -}} 12 | {%- endif %} 13 |
14 | {%- else %} 15 |
16 | {%- endif %} 17 |
-------------------------------------------------------------------------------- /formation/templates/formation/textarea_field.jinja: -------------------------------------------------------------------------------- 1 | {% extends "formation/text_input.jinja" -%} 2 | {%- block input_widget %} 3 | 8 | {%- endblock input_widget %} -------------------------------------------------------------------------------- /formation/templates/formation/url_display.jinja: -------------------------------------------------------------------------------- 1 |
2 |
{{ field.get_display_label() }}
3 | {%- if not field.is_empty() %} 4 | 9 | {%- else %} 10 |
11 | {%- endif %} 12 |
-------------------------------------------------------------------------------- /formation/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/formation/tests/__init__.py -------------------------------------------------------------------------------- /formation/tests/utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | 4 | 5 | class PatchTranslationTestCase(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.add_apps_mock = unittest.mock.patch( 9 | 'django.utils.translation.trans_real.' 10 | 'DjangoTranslation._add_installed_apps_translations') 11 | self.add_apps_mock.start() 12 | 13 | def tearDown(self): 14 | self.add_apps_mock.stop() 15 | 16 | 17 | django_only = unittest.skipUnless( 18 | any('manage.py' in arg for arg in sys.argv), 19 | 'For Django test runner only') 20 | -------------------------------------------------------------------------------- /health_check/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/health_check/__init__.py -------------------------------------------------------------------------------- /health_check/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/health_check/tests/__init__.py -------------------------------------------------------------------------------- /health_check/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.urls import reverse 3 | from user_accounts.tests.mock import fake_superuser, fake_password 4 | 5 | 6 | class TestURLPatterns(TestCase): 7 | 8 | def test_ok(self): 9 | response = self.client.get(reverse('health_check-ok')) 10 | self.assertEqual(response.status_code, 200) 11 | self.assertContains(response, "Everything seems fine") 12 | 13 | def test_error(self): 14 | su = fake_superuser() 15 | self.client.login(username=su.username, password=fake_password) 16 | with self.assertRaises(Exception) as context: 17 | response = self.client.get(reverse('health_check-error')) 18 | self.assertTrue('This is a Test' in context.exception) 19 | -------------------------------------------------------------------------------- /health_check/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.contrib.auth.decorators import user_passes_test 3 | from django.http import HttpResponse 4 | 5 | 6 | def ok(request, *args, **kwargs): 7 | return HttpResponse("Everything seems fine", status=200) 8 | 9 | 10 | @user_passes_test(lambda u: u.is_superuser) 11 | def error(request, *args, **kwargs): 12 | raise Exception("This is a Test") 13 | 14 | 15 | urlpatterns = [ 16 | url(r'^$', ok, name='health_check-ok'), 17 | url(r'^error$', error, name='health_check-error'), 18 | ] 19 | -------------------------------------------------------------------------------- /helper_commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/helper_commands/__init__.py -------------------------------------------------------------------------------- /helper_commands/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HelperCommandsConfig(AppConfig): 5 | name = 'helper_commands' 6 | -------------------------------------------------------------------------------- /helper_commands/management/commands/run_debug_task.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from intake.tasks import debug_task 3 | 4 | 5 | class Command(BaseCommand): 6 | help = str( 7 | "[THIS IS NOT FOR USE ON PERSONAL MACHINES]" 8 | "Downloads from s3 and loads data.") 9 | 10 | def handle(self, *args, **kwargs): 11 | debug_task('test single arguement') 12 | -------------------------------------------------------------------------------- /intake/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | default_app_config = 'intake.apps.IntakeConfig' 3 | -------------------------------------------------------------------------------- /intake/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from intake import models 3 | 4 | 5 | @admin.register(models.FillablePDF) 6 | class FillablePDFAdmin(admin.ModelAdmin): 7 | pass 8 | 9 | 10 | @admin.register(models.County) 11 | class CountyAdmin(admin.ModelAdmin): 12 | pass 13 | 14 | 15 | @admin.register(models.StatusType) 16 | class StatusTypeAdmin(admin.ModelAdmin): 17 | pass 18 | 19 | 20 | @admin.register(models.NextStep) 21 | class NextStepAdmin(admin.ModelAdmin): 22 | pass 23 | 24 | 25 | @admin.register(models.StatusUpdate) 26 | class StatusUpdateAdmin(admin.ModelAdmin): 27 | pass 28 | 29 | 30 | @admin.register(models.StatusNotification) 31 | class StatusNotificationAdmin(admin.ModelAdmin): 32 | pass 33 | -------------------------------------------------------------------------------- /intake/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class IntakeConfig(AppConfig): 5 | name = 'intake' 6 | -------------------------------------------------------------------------------- /intake/fixtures/0015_add_default_counties_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "intake.county", 4 | "pk": 1, 5 | "fields": { 6 | "slug": "sanfrancisco", 7 | "name": "San Francisco", 8 | "description": "San Francisco" 9 | } 10 | }, 11 | { 12 | "model": "intake.county", 13 | "pk": 2, 14 | "fields": { 15 | "slug": "contracosta", 16 | "name": "Contra Costa", 17 | "description": "Conta Costa County (near Richmond, Concord, Walnut Creek, San Ramon, Antioch, or Brentwood)" 18 | } 19 | }, 20 | { 21 | "model": "intake.county", 22 | "pk": 3, 23 | "fields": { 24 | "slug": "alameda", 25 | "name": "Alameda", 26 | "description": "Alameda County (near Oakland, Berkeley, San Leandro, Hayward, Union City, Pleasanton, or Livermore)" 27 | } 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /intake/forms/__init__.py: -------------------------------------------------------------------------------- 1 | from .status_update_forms import ( 2 | StatusUpdateForm, StatusNotificationForm, 3 | NotificationContactInfoDisplayForm) 4 | from .application_transfer_form import ApplicationTransferForm 5 | 6 | __all__ = [ 7 | StatusUpdateForm, 8 | StatusNotificationForm, 9 | NotificationContactInfoDisplayForm, 10 | ApplicationTransferForm 11 | ] 12 | -------------------------------------------------------------------------------- /intake/groups.py: -------------------------------------------------------------------------------- 1 | FOLLOWUP_STAFF = 'followup_staff' 2 | APPLICATION_REVIEWERS = 'application_reviewers' 3 | PERFORMANCE_MONITORS = 'performance_monitors' 4 | -------------------------------------------------------------------------------- /intake/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/management/__init__.py -------------------------------------------------------------------------------- /intake/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/management/commands/__init__.py -------------------------------------------------------------------------------- /intake/management/commands/heroku_release.py: -------------------------------------------------------------------------------- 1 | from django.core import management 2 | from django.core.management.base import BaseCommand 3 | 4 | 5 | class Command(BaseCommand): 6 | help = str( 7 | "Migrates and loads essential data from fixtures") 8 | 9 | def handle(self, *args, **kwargs): 10 | management.call_command('migrate') 11 | management.call_command('load_essential_data') 12 | -------------------------------------------------------------------------------- /intake/management/commands/load_2000_mock_submissions.py: -------------------------------------------------------------------------------- 1 | from django.core.management import BaseCommand 2 | 3 | from intake.tests.mock import build_2000_mock_submissions 4 | 5 | 6 | class Command(BaseCommand): 7 | help = 'Generates new json sample fixtures' 8 | 9 | def handle(self, *args, **options): 10 | build_2000_mock_submissions() 11 | self.stdout.write( 12 | self.style.SUCCESS( 13 | "Created fake submissions, bundles, and transfers " 14 | "and saved them to fixture files")) 15 | -------------------------------------------------------------------------------- /intake/management/commands/load_essential_data.py: -------------------------------------------------------------------------------- 1 | from django.core import management 2 | from django.core.management.base import BaseCommand 3 | from project.fixtures_index import ESSENTIAL_DATA_FIXTURES 4 | 5 | 6 | class Command(BaseCommand): 7 | help = str( 8 | "Loads 'essential' data from fixture files") 9 | 10 | def handle(self, *args, **kwargs): 11 | management.call_command('loaddata', *ESSENTIAL_DATA_FIXTURES) 12 | -------------------------------------------------------------------------------- /intake/management/commands/load_mock_data.py: -------------------------------------------------------------------------------- 1 | from django.core import management 2 | from django.core.management.base import BaseCommand 3 | from intake.models import pdfs 4 | from intake.tests import mock 5 | from project.fixtures_index import ALL_MOCK_DATA_FIXTURES 6 | 7 | 8 | class Command(BaseCommand): 9 | help = str( 10 | "Loads mock data from fixture files") 11 | 12 | def handle(self, *args, **kwargs): 13 | management.call_command( 14 | 'loaddata', *ALL_MOCK_DATA_FIXTURES) 15 | if pdfs.FillablePDF.objects.count() == 0: 16 | mock.fillable_pdf() 17 | -------------------------------------------------------------------------------- /intake/management/commands/prepare_review_env.py: -------------------------------------------------------------------------------- 1 | from django.core import management 2 | from django.core.management.base import BaseCommand 3 | 4 | 5 | class Command(BaseCommand): 6 | help = str( 7 | "Run commands required to set up a review application environment") 8 | 9 | def handle(self, *args, **kwargs): 10 | management.call_command('migrate', 'easyaudit') 11 | management.call_command('new_fixtures') 12 | -------------------------------------------------------------------------------- /intake/management/commands/send_followups.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | import intake.services.followups as FollowupsService 3 | from intake.utils import is_the_weekend 4 | 5 | 6 | class Command(BaseCommand): 7 | help = 'Sends an email about unopened applications' 8 | 9 | def handle(self, *args, **options): 10 | if not is_the_weekend(): 11 | FollowupsService.send_all_followups_that_are_due(after_id=465) 12 | self.stdout.write( 13 | self.style.SUCCESS("Successfully sent followups") 14 | ) 15 | -------------------------------------------------------------------------------- /intake/management/commands/send_unopened_apps_notification.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | import intake.services.bundles as BundlesService 4 | 5 | 6 | class Command(BaseCommand): 7 | help = 'Sends an email about unopened applications' 8 | 9 | def handle(self, *args, **options): 10 | BundlesService.count_unreads_and_send_notifications_to_orgs() 11 | self.stdout.write( 12 | self.style.SUCCESS("Successfully referred any unopened apps") 13 | ) 14 | -------------------------------------------------------------------------------- /intake/management/commands/test_fillpdf.py: -------------------------------------------------------------------------------- 1 | from django.core import management 2 | from django.core.management.base import BaseCommand 3 | 4 | from intake.models import pdfs 5 | from intake.models import form_submission 6 | 7 | 8 | class Command(BaseCommand): 9 | help = str( 10 | "Sets up seeds based on what environment it runs in.") 11 | 12 | def handle(self, *args, **kwargs): 13 | fillable = pdfs.FillablePDF.objects.first() 14 | sub = form_submission.FormSubmission.objects.first() 15 | filled = fillable.fill_for_submission(sub) 16 | print(filled) 17 | -------------------------------------------------------------------------------- /intake/migrations/0002_fillablepdf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9 on 2016-05-09 00:00 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 | ('intake', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='FillablePDF', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('pdf', models.FileField(upload_to='pdfs/')), 20 | ('translator', models.TextField()), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /intake/migrations/0003_fillablepdf_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9 on 2016-05-09 00:40 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 | ('intake', '0002_fillablepdf'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='fillablepdf', 17 | name='name', 18 | field=models.CharField(default='Sample pdf', max_length=50), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /intake/migrations/0009_auto_20160615_2323.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-06-15 23:23 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('intake', '0008_auto_20160615_2307'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='formsubmission', 17 | options={'ordering': ['-date_received']}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /intake/migrations/0011_confirmationsentlogentry_message_sent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-07-06 20:09 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 | ('intake', '0010_auto_20160706_2000'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='confirmationsentlogentry', 17 | name='message_sent', 18 | field=models.TextField(blank=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /intake/migrations/0012_auto_20160706_2010.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-07-06 20:10 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('intake', '0011_confirmationsentlogentry_message_sent'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameModel( 16 | old_name='ConfirmationSentLogEntry', 17 | new_name='ApplicantContactedLogEntry', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /intake/migrations/0014_county_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-08-16 18:39 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 | ('intake', '0013_add_county_model'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='county', 17 | name='name', 18 | field=models.TextField(default='San Francisco'), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /intake/migrations/0016_applicationlogentry_organization.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-08-12 22:46 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 | ('user_accounts', '0008_add_profiles_related_name_to_organization'), 13 | ('intake', '0015_add_default_counties'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='applicationlogentry', 19 | name='organization', 20 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='logs', to='user_accounts.Organization'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /intake/migrations/0017_fillablepdf_organization.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-08-12 23:02 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 | ('user_accounts', '0008_add_profiles_related_name_to_organization'), 13 | ('intake', '0016_applicationlogentry_organization'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='fillablepdf', 19 | name='organization', 20 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pdfs', to='user_accounts.Organization'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /intake/migrations/0020_formsubmission_organizations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-08-18 18:03 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 | ('user_accounts', '0010_add_slugs_to_orgs'), 12 | ('intake', '0019_update_contact_preferences_answer_data'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='formsubmission', 18 | name='organizations', 19 | field=models.ManyToManyField(related_name='submissions', to='user_accounts.Organization'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /intake/migrations/0024_app_bundle_pdf_not_required.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-08-30 23:23 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 | ('intake', '0023_applicationbundle'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='applicationbundle', 17 | name='bundled_pdf', 18 | field=models.FileField(blank=True, null=True, upload_to='pdf_bundles/'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /intake/migrations/0025_remove_formsubmission_counties.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-09-02 20:53 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('intake', '0024_app_bundle_pdf_not_required'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='formsubmission', 17 | name='counties', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /intake/migrations/0026_applicationlogentry_add_transfer_type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-09-20 01:30 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 | ('intake', '0025_remove_formsubmission_counties'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='applicationlogentry', 17 | name='event_type', 18 | field=models.PositiveSmallIntegerField(choices=[(1, 'opened'), (2, 'referred'), (3, 'processed'), (4, 'deleted'), (5, 'sent confirmation'), (6, 'referred to another org')]), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /intake/migrations/0028_formsubmission_applicant.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-09-20 22:05 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 | ('intake', '0027_applicant_applicationevent'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='formsubmission', 18 | name='applicant', 19 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='form_submissions', to='intake.Applicant'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /intake/migrations/0029_applicationevent_order_by_time.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-10-07 18:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('intake', '0028_formsubmission_applicant'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='applicationevent', 17 | options={'ordering': ['-time']}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /intake/migrations/0031_add_view_app_stats_permission.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-12-04 00:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('intake', '0030_add_visitor_model'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='applicant', 17 | options={'permissions': (('view_app_stats', 'Can see detailed aggregate information about apps'),)}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /intake/migrations/0032_add_view_app_details_permission.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-12-04 00:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('intake', '0031_add_view_app_stats_permission'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='applicant', 17 | options={'permissions': (('view_app_stats', 'Can see detailed aggregate information about apps'), ('view_app_details', 'Can see detail information about individual apps'))}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /intake/migrations/0033_visitor_ip_address.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-12-07 04:44 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 | ('intake', '0032_add_view_app_details_permission'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='visitor', 17 | name='ip_address', 18 | field=models.TextField(null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /intake/migrations/0036_add_view_app_notes_permission.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-12-27 23:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('intake', '0035_applicationnote'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='applicationnote', 17 | options={'ordering': ['-created'], 'permissions': (('intake.view_application_note', 'Can read the contents of notes from followups'),)}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /intake/migrations/0041_enforce_unique_slugs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.5 on 2017-01-24 18:58 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 | ('intake', '0040_create_status_notification'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='nextstep', 18 | name='slug', 19 | field=models.SlugField(unique=True), 20 | ), 21 | migrations.AlterField( 22 | model_name='statustype', 23 | name='slug', 24 | field=models.SlugField(unique=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /intake/migrations/0043_status_update_related_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2017-01-25 18:04 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 | ('intake', '0042_fix_permissions_and_non_db_model_updates'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='statusupdate', 18 | name='application', 19 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_updates', to='intake.Application'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /intake/migrations/0044_one_to_one_mapping_between_status_update_and_notification.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.5 on 2017-01-26 00:16 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 | ('intake', '0043_status_update_related_name'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='statusnotification', 18 | name='status_update', 19 | field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='notification', to='intake.StatusUpdate'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /intake/migrations/0045_add_is_active_to_template_options.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.5 on 2017-01-27 18:31 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 | ('intake', '0044_one_to_one_mapping_between_status_update_and_notification'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='nextstep', 17 | name='is_active', 18 | field=models.BooleanField(default=True), 19 | ), 20 | migrations.AddField( 21 | model_name='statustype', 22 | name='is_active', 23 | field=models.BooleanField(default=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /intake/migrations/0049_template_option_is_a_status_update_choice.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2017-03-10 00:24 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 | ('intake', '0048_template_field_validators'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='nextstep', 17 | name='is_a_status_update_choice', 18 | field=models.BooleanField(default=True), 19 | ), 20 | migrations.AddField( 21 | model_name='statustype', 22 | name='is_a_status_update_choice', 23 | field=models.BooleanField(default=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /intake/migrations/0050_application_created_updated.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2017-03-24 18: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 | ('intake', '0049_template_option_is_a_status_update_choice'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='application', 17 | name='created', 18 | field=models.DateTimeField(null=True), 19 | ), 20 | migrations.AddField( 21 | model_name='application', 22 | name='updated', 23 | field=models.DateTimeField(null=True), 24 | ) 25 | ] 26 | -------------------------------------------------------------------------------- /intake/migrations/0055_visitor_uuid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-25 23:02 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import uuid 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('intake', '0054_graduate_additional_fields'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='visitor', 18 | name='uuid', 19 | field=models.UUIDField(default=uuid.uuid4, editable=False), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /intake/migrations/0057_applicant_visitor_onetoone.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-26 01:05 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 | ('intake', '0056_backfill_visitors_for_applicants'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='applicant', 18 | name='visitor', 19 | field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='intake.Visitor'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /intake/migrations/0060_visitor_locale_user_agent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-07-18 22:25 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 | ('intake', '0059_prebuiltpdfbundle'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='visitor', 17 | name='locale', 18 | field=models.TextField(default=''), 19 | ), 20 | migrations.AddField( 21 | model_name='visitor', 22 | name='user_agent', 23 | field=models.TextField(default=''), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /intake/migrations/0071_add_display_order_to_template_option.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.8 on 2017-10-03 20:59 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 | ('intake', '0070_formsubmission_unlisted_counties'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='nextstep', 17 | name='display_order', 18 | field=models.IntegerField(default=0), 19 | ), 20 | migrations.AddField( 21 | model_name='statustype', 22 | name='display_order', 23 | field=models.IntegerField(default=0), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /intake/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/migrations/__init__.py -------------------------------------------------------------------------------- /intake/models/abstract_base_models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class BaseModel(models.Model): 5 | """An abstract base model that includes default fields for any table 6 | """ 7 | # these must be null = True because they will be added to preexisting 8 | # models 9 | created = models.DateTimeField(auto_now_add=True, null=True) 10 | updated = models.DateTimeField(auto_now=True, null=True) 11 | 12 | class Meta: 13 | abstract = True 14 | -------------------------------------------------------------------------------- /intake/models/application_transfer.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from .abstract_base_models import BaseModel 3 | 4 | 5 | class ApplicationTransfer(BaseModel): 6 | """A record that links a 'Transferred' status update to the new application 7 | created from the transfer. 8 | """ 9 | status_update = models.OneToOneField( 10 | 'intake.StatusUpdate', 11 | models.CASCADE, 12 | related_name='transfer') 13 | new_application = models.ForeignKey( 14 | 'intake.Application', 15 | models.PROTECT, 16 | related_name='incoming_transfers') 17 | reason = models.TextField(blank=True) 18 | 19 | def __str__(self): 20 | return '{} because "{}"'.format( 21 | self.status_update.__str__(), 22 | self.reason) 23 | -------------------------------------------------------------------------------- /intake/models/fields.py: -------------------------------------------------------------------------------- 1 | from django.contrib.postgres.fields import JSONField 2 | from intake import validators 3 | 4 | 5 | class ContactInfoJSONField(JSONField): 6 | """ 7 | A field for storing contact information that validates 8 | data against expected keys and structure 9 | """ 10 | 11 | def validate(self, value, model_instance): 12 | validators.contact_info_json(value) 13 | super().validate(value, model_instance) 14 | -------------------------------------------------------------------------------- /intake/models/next_step.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from .template_option import TemplateOption, TemplateOptionManager 3 | 4 | 5 | class PurgedNextStep(models.Model): 6 | """Placeholder for custom VIEW see intake migration 0067 7 | """ 8 | class Meta: 9 | db_table = 'purged\".\"intake_nextstep' 10 | managed = False 11 | 12 | 13 | class NextStep(TemplateOption): 14 | objects = TemplateOptionManager() 15 | -------------------------------------------------------------------------------- /intake/models/status_type.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from .template_option import TemplateOption, TemplateOptionManager 3 | 4 | 5 | class PurgedStatusType(models.Model): 6 | """Placeholder for custom VIEW see intake migration 0067 7 | """ 8 | class Meta: 9 | db_table = 'purged\".\"intake_statustype' 10 | managed = False 11 | 12 | 13 | class StatusType(TemplateOption): 14 | objects = TemplateOptionManager() 15 | -------------------------------------------------------------------------------- /intake/pdfparser.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/pdfparser.jar -------------------------------------------------------------------------------- /intake/permissions.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import Permission 2 | 3 | # apps 4 | CAN_SEE_APP_STATS = 'view_app_stats' 5 | CAN_SEE_APP_DETAILS = 'view_app_details' 6 | 7 | # notes 8 | CAN_SEE_FOLLOWUP_NOTES = 'view_application_note' 9 | 10 | 11 | def get_all_followup_permissions(): 12 | return Permission.objects.filter( 13 | codename__in=[ 14 | CAN_SEE_FOLLOWUP_NOTES, 15 | 'add_applicationnote', 16 | 'delete_applicationnote']) 17 | -------------------------------------------------------------------------------- /intake/serializers/applicant_serializer.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from intake.serializers.fields import ChainableAttributeField 3 | 4 | 5 | class ApplicantMixpanelSerializer(serializers.Serializer): 6 | """This serializes user information from an applicant instance 7 | """ 8 | applicant_source = ChainableAttributeField('visitor.source') 9 | applicant_referrer = ChainableAttributeField('visitor.referrer') 10 | -------------------------------------------------------------------------------- /intake/serializers/organization_serializer.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from user_accounts.models import Organization 3 | 4 | 5 | class OrganizationFollowupSerializer(serializers.ModelSerializer): 6 | county = serializers.SlugRelatedField(read_only=True, slug_field='name') 7 | 8 | class Meta: 9 | model = Organization 10 | fields = [ 11 | 'slug', 'name', 'county', 12 | 'short_followup_message', 13 | 'long_followup_message'] 14 | 15 | 16 | class OrganizationSerializer(serializers.ModelSerializer): 17 | 18 | class Meta: 19 | model = Organization 20 | fields = ['slug', 'name', 'is_live'] 21 | -------------------------------------------------------------------------------- /intake/serializers/shortcuts.py: -------------------------------------------------------------------------------- 1 | from .applicant_serializer import ApplicantMixpanelSerializer 2 | from .request_serializer import RequestSerializer 3 | from .view_serializer import ViewMixpanelSerializer 4 | 5 | 6 | def mixpanel_applicant_data(applicant): 7 | return ApplicantMixpanelSerializer(applicant).data 8 | 9 | 10 | def mixpanel_request_data(request): 11 | return RequestSerializer(request).data 12 | 13 | 14 | def mixpanel_view_data(view): 15 | return ViewMixpanelSerializer(view).data 16 | -------------------------------------------------------------------------------- /intake/serializers/tag_serializer.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from taggit.models import Tag 3 | 4 | 5 | class TagSerializer(serializers.ModelSerializer): 6 | 7 | class Meta: 8 | model = Tag 9 | fields = ['id', 'name'] 10 | -------------------------------------------------------------------------------- /intake/serializers/view_serializer.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .fields import ChainableAttributeField 3 | from intake.constants import LANGUAGES_LOOKUP 4 | 5 | 6 | class ViewMixpanelSerializer(serializers.Serializer): 7 | view_name = ChainableAttributeField('__class__.__name__') 8 | -------------------------------------------------------------------------------- /intake/service_objects/__init__.py: -------------------------------------------------------------------------------- 1 | from .applicant_notifications import ( 2 | FollowupNotification, 3 | ConfirmationNotification 4 | ) 5 | 6 | __all__ = [ 7 | FollowupNotification, 8 | ConfirmationNotification 9 | ] 10 | -------------------------------------------------------------------------------- /intake/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/services/__init__.py -------------------------------------------------------------------------------- /intake/services/messages_service.py: -------------------------------------------------------------------------------- 1 | from django.contrib import messages 2 | 3 | 4 | def flash_errors(request, *errors): 5 | for error in errors: 6 | messages.error(request, error) 7 | 8 | 9 | def flash_success(request, *success_messages): 10 | for success in success_messages: 11 | messages.success(request, success) 12 | 13 | 14 | def flash_warnings(request, *warning_messages): 15 | for warning in warning_messages: 16 | messages.warning(request, warning) 17 | -------------------------------------------------------------------------------- /intake/services/pagination.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 2 | 3 | 4 | def get_page(qset, page_index, max_count_per_page=25, min_count_per_page=5): 5 | paginator = Paginator( 6 | qset, max_count_per_page, orphans=min_count_per_page) 7 | try: 8 | return paginator.page(page_index) 9 | except PageNotAnInteger: 10 | return paginator.page(1) 11 | except EmptyPage: 12 | if int(page_index) <= 0: 13 | return paginator.page(1) 14 | else: 15 | return paginator.page(paginator.num_pages) 16 | 17 | 18 | def get_serialized_page(qset, serializer, page_index, **kwargs): 19 | page = get_page(qset, page_index, **kwargs) 20 | page.object_list = serializer(page.object_list, many=True).data 21 | return page 22 | -------------------------------------------------------------------------------- /intake/static/intake/fonts: -------------------------------------------------------------------------------- 1 | ../../../node_modules/bootstrap/fonts/ -------------------------------------------------------------------------------- /intake/static/intake/icomoon/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/icomoon/icomoon.eot -------------------------------------------------------------------------------- /intake/static/intake/icomoon/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/icomoon/icomoon.ttf -------------------------------------------------------------------------------- /intake/static/intake/icomoon/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/icomoon/icomoon.woff -------------------------------------------------------------------------------- /intake/static/intake/img/10_minutes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/10_minutes.png -------------------------------------------------------------------------------- /intake/static/intake/img/10_minutes@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/10_minutes@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/CMR-hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/CMR-hero.png -------------------------------------------------------------------------------- /intake/static/intake/img/CMR_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/CMR_share.png -------------------------------------------------------------------------------- /intake/static/intake/img/CMR_social_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/CMR_social_thumbnail.png -------------------------------------------------------------------------------- /intake/static/intake/img/alameda_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/alameda_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/alameda_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/alameda_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/ben-golder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/ben-golder.jpg -------------------------------------------------------------------------------- /intake/static/intake/img/bundle-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/bundle-icon.png -------------------------------------------------------------------------------- /intake/static/intake/img/bundle-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/bundle-icon@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/ca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/ca.png -------------------------------------------------------------------------------- /intake/static/intake/img/ca@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/ca@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/cfa-logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/cfa-logo-black.png -------------------------------------------------------------------------------- /intake/static/intake/img/cfa-logo-black@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/cfa-logo-black@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/cfa_logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/cfa_logo@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/cfa_logo_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/cfa_logo_color.png -------------------------------------------------------------------------------- /intake/static/intake/img/cfa_logo_color@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/cfa_logo_color@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/cfa_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/cfa_logo_white.png -------------------------------------------------------------------------------- /intake/static/intake/img/cmr_logo_black_cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/cmr_logo_black_cropped.png -------------------------------------------------------------------------------- /intake/static/intake/img/cmr_logo_black_cropped@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/cmr_logo_black_cropped@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/contra_costa_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/contra_costa_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/contra_costa_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/contra_costa_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/eligibility-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/eligibility-icon.png -------------------------------------------------------------------------------- /intake/static/intake/img/eligibility-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/eligibility-icon@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/fresno_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/fresno_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/fresno_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/fresno_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/geometry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/geometry.png -------------------------------------------------------------------------------- /intake/static/intake/img/jazmyn-latimer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/jazmyn-latimer.jpg -------------------------------------------------------------------------------- /intake/static/intake/img/job_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/job_search.png -------------------------------------------------------------------------------- /intake/static/intake/img/job_search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/job_search@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/ladders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/ladders.png -------------------------------------------------------------------------------- /intake/static/intake/img/ladders@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/ladders@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/notification-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/notification-icon.png -------------------------------------------------------------------------------- /intake/static/intake/img/notification-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/notification-icon@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/official_doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/official_doc.png -------------------------------------------------------------------------------- /intake/static/intake/img/official_doc@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/official_doc@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/ok_hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/ok_hand.png -------------------------------------------------------------------------------- /intake/static/intake/img/ok_hand@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/ok_hand@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/partner-interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/partner-interface.png -------------------------------------------------------------------------------- /intake/static/intake/img/partner-interface@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/partner-interface@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/pencil.png -------------------------------------------------------------------------------- /intake/static/intake/img/pencil@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/pencil@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/san_diego_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/san_diego_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/san_diego_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/san_diego_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/san_francisco_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/san_francisco_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/san_francisco_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/san_francisco_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/santa_barbara_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/santa_barbara_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/santa_barbara_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/santa_barbara_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/santa_clara_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/santa_clara_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/santa_clara_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/santa_clara_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/santa_cruz_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/santa_cruz_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/santa_cruz_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/santa_cruz_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/solano_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/solano_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/solano_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/solano_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/speech_bubbles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/speech_bubbles.png -------------------------------------------------------------------------------- /intake/static/intake/img/speech_bubbles@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/speech_bubbles@2x.png -------------------------------------------------------------------------------- /intake/static/intake/img/tiffany-andrews.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/tiffany-andrews.jpg -------------------------------------------------------------------------------- /intake/static/intake/img/ventura_seal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/ventura_seal.png -------------------------------------------------------------------------------- /intake/static/intake/img/ventura_seal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/intake/img/ventura_seal@2x.png -------------------------------------------------------------------------------- /intake/static/intake/js/ajax.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | function handleAjaxFormSubmission(form, successCallback){ 4 | var actionUrl = form.attr('action'); 5 | var rawData = form.serializeArray(); 6 | var data = {}; 7 | rawData.forEach(function (field){ data[field.name] = field.value; }); 8 | $.post(actionUrl, data, successCallback); 9 | } 10 | 11 | module.exports = { 12 | handleForm: handleAjaxFormSubmission, 13 | } -------------------------------------------------------------------------------- /intake/static/intake/js/csrf.js: -------------------------------------------------------------------------------- 1 | 2 | function csrfSafeMethod(method) { 3 | // these HTTP methods do not require CSRF protection 4 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 5 | } 6 | 7 | function setupAjaxCsrf(jQueryObject, csrftoken) { 8 | jQueryObject.ajaxSetup({ 9 | beforeSend: function(xhr, settings) { 10 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 11 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 12 | } 13 | } 14 | }); 15 | 16 | } 17 | 18 | module.exports = { 19 | setupAjaxCsrf: setupAjaxCsrf 20 | }; 21 | -------------------------------------------------------------------------------- /intake/static/intake/js/d3.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | var d3 = require('d3-selection'); 3 | utils.combineObjs(d3, require('d3-array')); 4 | utils.combineObjs(d3, require('d3-collection')); 5 | utils.combineObjs(d3, require('d3-format')); 6 | utils.combineObjs(d3, require('d3-axis')); 7 | utils.combineObjs(d3, require('d3-scale')); 8 | utils.combineObjs(d3, require('d3-shape')); 9 | utils.combineObjs(d3, require('d3-time')); 10 | utils.combineObjs(d3, require('d3-time-format')); 11 | 12 | module.exports = d3; -------------------------------------------------------------------------------- /intake/static/intake/less/charts.less: -------------------------------------------------------------------------------- 1 | .stats-single_figure { 2 | display: inline-block; 3 | width: 15em; 4 | } 5 | .minutes span.stats-total-number, .minutes span.stats-total-annot { 6 | display: block; 7 | } 8 | 9 | .stats-total-number { 10 | font-size: 2em; 11 | font-weight: 500; 12 | } 13 | span.perc-sign { 14 | font-size: 0.8em; 15 | } 16 | 17 | svg g.axis text { 18 | font-family: @font-family-sans-serif; 19 | } 20 | 21 | .axis.y path { stroke-width: 0; } 22 | .axis.y line { stroke: #dfdfdf; } 23 | 24 | rect.day-bar { 25 | stroke: #fff; 26 | stroke-width: 0.5px; 27 | fill: @brand-primary; 28 | } 29 | 30 | path.day-line { 31 | stroke: @brand-primary; 32 | stroke-width: 2px; 33 | fill: none; 34 | } 35 | -------------------------------------------------------------------------------- /intake/static/intake/less/clear.less: -------------------------------------------------------------------------------- 1 | ul { 2 | list-style-type: none; 3 | } -------------------------------------------------------------------------------- /intake/static/intake/less/flash_messages.less: -------------------------------------------------------------------------------- 1 | 2 | .flash_message { 3 | padding: .25em 0; 4 | } 5 | .flash_message:first-child { 6 | padding-top: 1em; 7 | } 8 | .flash_message:last-child { 9 | padding-bottom: 1em; 10 | } 11 | .flash_message .row { 12 | position: relative; 13 | } 14 | .flash_message .row > div:before { 15 | .glyphicon; 16 | width: 2em; 17 | position: absolute; 18 | } 19 | .flash_message-body { 20 | display: block; 21 | } 22 | 23 | .flash_message.error { 24 | background-color: @brand-danger-background; 25 | color: @brand-danger-text; 26 | } 27 | 28 | .flash_message.success { 29 | background-color: @brand-success; 30 | color: white; 31 | } 32 | 33 | .flash_message.warning { 34 | background-color: @brand-warning; 35 | color: white; 36 | } 37 | -------------------------------------------------------------------------------- /intake/static/intake/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Function borrowed from getbootstrap.com/css/#mixins 2 | // Generate the medium columns 3 | .make-md-column(@columns, @gutter: @grid-gutter-width) { 4 | position: relative; 5 | // Prevent columns from collapsing when empty 6 | min-height: 1px; 7 | // Inner gutter via padding 8 | padding-left: (@gutter / 2); 9 | padding-right: (@gutter / 2); 10 | 11 | // Calculate width based on number of columns available 12 | @media (min-width: @screen-md-min) { 13 | float: left; 14 | width: percentage((@columns / @grid-columns)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /intake/static/intake/less/sunset-notice.less: -------------------------------------------------------------------------------- 1 | .sunset-notice { 2 | h3 { 3 | font-weight: bold; 4 | margin-bottom: 22px; 5 | } 6 | h4 { 7 | font-size: 18px; 8 | } 9 | ul.list--bulleted { 10 | list-style-type: disc; 11 | } 12 | hr { 13 | margin: 40px 0; 14 | } 15 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/_ie-hacks.scss: -------------------------------------------------------------------------------- 1 | .lt-ie10 { 2 | .select { 3 | &:before { 4 | display: none; 5 | } 6 | } 7 | .select__element { 8 | padding-right: .7em; 9 | } 10 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/_shame.scss: -------------------------------------------------------------------------------- 1 | /* this is a file for shameful hacks, so they can act as temporary solutions 2 | If you put something here, fix it and take it out later :) 3 | */ -------------------------------------------------------------------------------- /intake/static/intake/scss/atoms/_base.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | 6 | html { 7 | position: relative; 8 | background-color: $color-background; 9 | font-size: $em-base; 10 | -webkit-font-smoothing: antialiased; 11 | min-height: 100%; 12 | } 13 | 14 | body { 15 | font-size: $font-size-normal; 16 | font-family: $font-system; 17 | line-height: $line-height-normal; 18 | margin: 0; 19 | } 20 | 21 | .page-wrapper { 22 | overflow: hidden; 23 | padding-left: $site-margins/2; 24 | padding-right: $site-margins/2; 25 | @include media($tablet-up) { 26 | padding-left: $site-margins; 27 | padding-right: $site-margins; 28 | } 29 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/atoms/_emoji.scss: -------------------------------------------------------------------------------- 1 | .emoji { 2 | display: inline-block; 3 | text-indent: -9999px; 4 | height: 1em; 5 | width: 1em; 6 | content: ''; 7 | background-size: 100% auto; 8 | position: relative; 9 | } 10 | 11 | .emoji--big { 12 | width: 3em; 13 | height: 3em; 14 | } 15 | 16 | .emoji--med { 17 | width: 2.4em; 18 | height: 2.4em; 19 | } 20 | 21 | .emoji--small { 22 | width: 1.8em; 23 | height: 1.8em; 24 | } 25 | 26 | // .emoji--grinning-face { 27 | // background-image: image-url('emojis/1f600.png'); 28 | // } 29 | -------------------------------------------------------------------------------- /intake/static/intake/scss/atoms/_form-widths.scss: -------------------------------------------------------------------------------- 1 | .form-width--short { 2 | max-width: $width-form-short; 3 | } 4 | 5 | .form-width--med { 6 | max-width: $width-form-med; 7 | } 8 | 9 | .form-width--long { 10 | max-width: $width-form-long; 11 | } 12 | 13 | .form-width--casenumber { 14 | max-width: $width-form-casenumber; 15 | } 16 | 17 | .form-width--name { 18 | max-width: $width-form-name; 19 | } 20 | 21 | .form-width--phone { 22 | max-width: $width-form-phone; 23 | } 24 | 25 | .form-width--ssn { 26 | max-width: $width-form-ssn; 27 | } 28 | 29 | .form-width--zip { 30 | max-width: $width-form-zip; 31 | } 32 | 33 | .form-width--searchbar { 34 | max-width: $width-form-searchbar; 35 | } 36 | -------------------------------------------------------------------------------- /intake/static/intake/scss/atoms/_images.scss: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | height: auto; 4 | width: auto; 5 | } 6 | -------------------------------------------------------------------------------- /intake/static/intake/scss/atoms/_labels.scss: -------------------------------------------------------------------------------- 1 | .label { 2 | display: inline-block; 3 | font-size: $font-size-smallest; 4 | background-color: $color-dark-grey; 5 | color: #fff; 6 | font-weight: 500; 7 | border-radius: $border-radius; 8 | text-transform: uppercase; 9 | padding: 0 .5em; 10 | line-height: 1.4em; 11 | position: relative; 12 | top: -.1em; 13 | margin-left: .2em; 14 | } 15 | 16 | .label--magenta { 17 | background-color: $color-magenta; 18 | } 19 | 20 | .label--teal { 21 | background-color: $color-teal; 22 | } 23 | 24 | .label--green { 25 | background-color: $color-green; 26 | } 27 | 28 | .label--orange { 29 | background-color: $color-orange; 30 | } 31 | 32 | .label--red { 33 | background-color: $color-red; 34 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/helpers/_buttons-list.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// A list of all HTML button elements. 4 | /// 5 | /// @type list 6 | /// 7 | /// @access private 8 | 9 | $_buttons-list: ( 10 | "button", 11 | "[type='button']", 12 | "[type='reset']", 13 | "[type='submit']", 14 | ); 15 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/helpers/_scales.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | //// 4 | /// Pre-defined scales for use with the `modular-scale` function. 5 | /// 6 | /// @type number (unitless) 7 | /// 8 | /// @see {function} modular-scale 9 | //// 10 | 11 | $minor-second: 1.067; 12 | $major-second: 1.125; 13 | $minor-third: 1.2; 14 | $major-third: 1.25; 15 | $perfect-fourth: 1.333; 16 | $augmented-fourth: 1.414; 17 | $perfect-fifth: 1.5; 18 | $minor-sixth: 1.6; 19 | $golden: 1.618; 20 | $major-sixth: 1.667; 21 | $minor-seventh: 1.778; 22 | $major-seventh: 1.875; 23 | $octave: 2; 24 | $major-tenth: 2.5; 25 | $major-eleventh: 2.667; 26 | $major-twelfth: 3; 27 | $double-octave: 4; 28 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/helpers/_text-inputs-list.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// A list of all _text-based_ HTML inputs. 4 | /// 5 | /// @type list 6 | /// 7 | /// @access private 8 | 9 | $_text-inputs-list: ( 10 | "[type='color']", 11 | "[type='date']", 12 | "[type='datetime']", 13 | "[type='datetime-local']", 14 | "[type='email']", 15 | "[type='month']", 16 | "[type='number']", 17 | "[type='password']", 18 | "[type='search']", 19 | "[type='tel']", 20 | "[type='text']", 21 | "[type='time']", 22 | "[type='url']", 23 | "[type='week']", 24 | "input:not([type])", 25 | "textarea", 26 | ); 27 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/library/_border-style.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Provides a concise, one-line method for setting `border-style` on specific 4 | /// edges of a box. Use a `null` value to “skip” edges of the box with standard 5 | /// CSS shorthand. 6 | /// 7 | /// @argument {list} $values 8 | /// List of border styles; accepts CSS shorthand. 9 | /// 10 | /// @example scss 11 | /// .element { 12 | /// @include border-style(dashed null solid); 13 | /// } 14 | /// 15 | /// // CSS Output 16 | /// .element { 17 | /// border-bottom-style: solid; 18 | /// border-top-style: dashed; 19 | /// } 20 | /// 21 | /// @require {mixin} _directional-property 22 | 23 | @mixin border-style($values) { 24 | @include _directional-property(border, style, $values); 25 | } 26 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/library/_border-width.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Provides a concise, one-line method for setting `border-width` on specific 4 | /// edges of a box. Use a `null` value to “skip” edges of the box with standard 5 | /// CSS shorthand. 6 | /// 7 | /// @argument {list} $values 8 | /// List of border widths; accepts CSS shorthand. 9 | /// 10 | /// @example scss 11 | /// .element { 12 | /// @include border-width(1em null 20px); 13 | /// } 14 | /// 15 | /// // CSS Output 16 | /// .element { 17 | /// border-bottom-width: 20px; 18 | /// border-top-width: 1em; 19 | /// } 20 | /// 21 | /// @require {mixin} _directional-property 22 | 23 | @mixin border-width($values) { 24 | @include _directional-property(border, width, $values); 25 | } 26 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/library/_clearfix.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Provides an easy way to include a clearfix for containing floats. 4 | /// 5 | /// @link https://goo.gl/yP5hiZ 6 | /// 7 | /// @example scss 8 | /// .element { 9 | /// @include clearfix; 10 | /// } 11 | /// 12 | /// // CSS Output 13 | /// .element::after { 14 | /// clear: both; 15 | /// content: ""; 16 | /// display: block; 17 | /// } 18 | 19 | @mixin clearfix { 20 | &::after { 21 | clear: both; 22 | content: ""; 23 | display: block; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/library/_hide-text.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Hides the text in an element, commonly used to show an image instead. Some 4 | /// elements will need block-level styles applied. 5 | /// 6 | /// @link https://goo.gl/EvLRIu 7 | /// 8 | /// @example scss 9 | /// .element { 10 | /// @include hide-text; 11 | /// } 12 | /// 13 | /// // CSS Output 14 | /// .element { 15 | /// overflow: hidden; 16 | /// text-indent: 101%; 17 | /// white-space: nowrap; 18 | /// } 19 | 20 | @mixin hide-text { 21 | overflow: hidden; 22 | text-indent: 101%; 23 | white-space: nowrap; 24 | } 25 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/library/_shade.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Mixes a color with black. 4 | /// 5 | /// @argument {color} $color 6 | /// 7 | /// @argument {number (percentage)} $percent 8 | /// The amount of black to be mixed in. 9 | /// 10 | /// @return {color} 11 | /// 12 | /// @example scss 13 | /// .element { 14 | /// background-color: shade(#ffbb52, 60%); 15 | /// } 16 | /// 17 | /// // CSS Output 18 | /// .element { 19 | /// background-color: #664a20; 20 | /// } 21 | 22 | @function shade( 23 | $color, 24 | $percent 25 | ) { 26 | @if not _is-color($color) { 27 | @error "`#{$color}` is not a valid color for the `$color` argument in " + 28 | "the `shade` mixin."; 29 | } @else { 30 | @return mix(#000, $color, $percent); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/library/_strip-unit.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Strips the unit from a number. 4 | /// 5 | /// @argument {number} $value 6 | /// 7 | /// @return {number (unitless)} 8 | /// 9 | /// @example scss 10 | /// $dimension: strip-unit(10em); 11 | /// 12 | /// // Output 13 | /// $dimension: 10; 14 | 15 | @function strip-unit($value) { 16 | @return ($value / ($value * 0 + 1)); 17 | } 18 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/library/_tint.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Mixes a color with white. 4 | /// 5 | /// @argument {color} $color 6 | /// 7 | /// @argument {number (percentage)} $percent 8 | /// The amount of white to be mixed in. 9 | /// 10 | /// @return {color} 11 | /// 12 | /// @example scss 13 | /// .element { 14 | /// background-color: tint(#6ecaa6, 40%); 15 | /// } 16 | /// 17 | /// // CSS Output 18 | /// .element { 19 | /// background-color: #a8dfc9; 20 | /// } 21 | 22 | @function tint( 23 | $color, 24 | $percent 25 | ) { 26 | @if not _is-color($color) { 27 | @error "`#{$color}` is not a valid color for the `$color` argument in " + 28 | "the `tint` mixin."; 29 | } @else { 30 | @return mix(#fff, $color, $percent); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/utilities/_assign-inputs.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Append pseudo-classes to a selector(s). 4 | /// 5 | /// @argument {list | string} $inputs 6 | /// A selector, or list of selectors, to apply the pseudo-class to. 7 | /// 8 | /// @argument {pseudo-class} $pseudo [null] 9 | /// The pseudo-class to be appended. 10 | /// 11 | /// @return {list} 12 | /// 13 | /// @access private 14 | 15 | @function _assign-inputs( 16 | $inputs, 17 | $pseudo: null 18 | ) { 19 | $list: (); 20 | 21 | @each $input in $inputs { 22 | $input: unquote($input); 23 | $input: if($pseudo, $input + ":" + $pseudo, $input); 24 | $list: append($list, $input, comma); 25 | } 26 | 27 | @return $list; 28 | } 29 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/utilities/_fetch-bourbon-setting.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Return a Bourbon setting. 4 | /// 5 | /// @argument {string} $setting 6 | /// 7 | /// @return {boolean | color | list | number | string} 8 | /// 9 | /// @example scss 10 | /// _fetch-bourbon-setting(rails-asset-pipeline) 11 | /// 12 | /// @access private 13 | 14 | @function _fetch-bourbon-setting($setting) { 15 | @return map-get(map-merge($_bourbon-defaults, $bourbon), $setting); 16 | } 17 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/utilities/_gamma.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Performs gamma correction on a single color channel. 4 | /// 5 | /// Note that the calculation is approximate if a `pow()` is not available. 6 | /// 7 | /// @argument {number (0-1)} $channel 8 | /// 9 | /// @return {number (0-1)} 10 | /// 11 | /// @access private 12 | 13 | @function _gamma($channel) { 14 | @if $channel < 0.03928 { 15 | @return $channel / 12.92; 16 | } @else { 17 | $c: ($channel + 0.055) / 1.055; 18 | @if function-exists("pow") { 19 | @return pow($c, 2.4); 20 | } @else { 21 | @return 0.56 * $c * $c * $c + 0.44 * $c * $c; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/utilities/_lightness.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Programatically determines the lightness of a color. 4 | /// 5 | /// @argument {color (hex)} $hex-color 6 | /// 7 | /// @return {number (0-1)} 8 | /// 9 | /// @example scss 10 | /// _lightness($color) 11 | /// 12 | /// @access private 13 | 14 | @function _lightness($hex-color) { 15 | $-local-red-raw: red(rgba($hex-color, 1)); 16 | $-local-green-raw: green(rgba($hex-color, 1)); 17 | $-local-blue-raw: blue(rgba($hex-color, 1)); 18 | 19 | $-local-red: _gamma($-local-red-raw / 255); 20 | $-local-green: _gamma($-local-green-raw / 255); 21 | $-local-blue: _gamma($-local-blue-raw / 255); 22 | 23 | @return $-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722; 24 | } 25 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/validators/_contains-falsy.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Checks if a list does not contain any values. 4 | /// 5 | /// @argument {list} $list 6 | /// The list to check against. 7 | /// 8 | /// @return {boolean} 9 | /// 10 | /// @access private 11 | 12 | @function _contains-falsy($list) { 13 | @each $item in $list { 14 | @if not $item { 15 | @return true; 16 | } 17 | } 18 | 19 | @return false; 20 | } 21 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/validators/_contains.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Checks if a list contains a value(s). 4 | /// 5 | /// @argument {list} $list 6 | /// The list to check against. 7 | /// 8 | /// @argument {list} $values 9 | /// A single value or list of values to check for. 10 | /// 11 | /// @return {boolean} 12 | /// 13 | /// @access private 14 | 15 | @function _contains( 16 | $list, 17 | $values... 18 | ) { 19 | @each $value in $values { 20 | @if type-of(index($list, $value)) != "number" { 21 | @return false; 22 | } 23 | } 24 | 25 | @return true; 26 | } 27 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/validators/_is-color.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Checks for a valid CSS color. 4 | /// 5 | /// @argument {string} $color 6 | /// 7 | /// @return {boolean} 8 | /// 9 | /// @access private 10 | 11 | @function _is-color($color) { 12 | @return (type-of($color) == color) or ($color == "currentColor"); 13 | } 14 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/validators/_is-length.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Checks for a valid CSS length. 4 | /// 5 | /// @argument {string} $value 6 | /// 7 | /// @return {boolean} 8 | /// 9 | /// @access private 10 | 11 | @function _is-length($value) { 12 | @return type-of($value) != "null" and (str-slice($value + "", 1, 4) == "calc" 13 | or index(auto inherit initial 0, $value) 14 | or (type-of($value) == "number" and not(unitless($value)))); 15 | } 16 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/validators/_is-number.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Checks for a valid number. 4 | /// 5 | /// @argument {number} $value 6 | /// 7 | /// @require {function} _contains 8 | /// 9 | /// @return {boolean} 10 | /// 11 | /// @access private 12 | 13 | @function _is-number($value) { 14 | @return _contains("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" 0 1 2 3 4 5 6 7 8 9, $value); 15 | } 16 | -------------------------------------------------------------------------------- /intake/static/intake/scss/bourbon/bourbon/validators/_is-size.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Checks for a valid CSS size. 4 | /// 5 | /// @argument {string} $value 6 | /// 7 | /// @return {boolean} 8 | /// 9 | /// @require {function} _contains 10 | /// 11 | /// @require {function} _is-length 12 | /// 13 | /// @access private 14 | 15 | @function _is-size($value) { 16 | @return _is-length($value) 17 | or _contains("fill" "fit-content" "min-content" "max-content", $value); 18 | } 19 | -------------------------------------------------------------------------------- /intake/static/intake/scss/molecules/_flashes.scss: -------------------------------------------------------------------------------- 1 | .flash { 2 | @include full-bleed(); 3 | position: relative; 4 | padding: { 5 | top: 1em; 6 | bottom: 1em; 7 | } 8 | background-color: #fff; 9 | font-weight: 500; 10 | &:last-child { 11 | box-shadow: 10px 0 10px rgba(0,0,0,.25); 12 | } 13 | p:last-child { 14 | margin-bottom: 0; 15 | } 16 | a { 17 | color: inherit; 18 | } 19 | } 20 | 21 | .flash.error { 22 | background-color: tint($color-red, 80%); 23 | } 24 | 25 | .flash.success { 26 | color: #fff; 27 | background-color: shade($color-green, 8%); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /intake/static/intake/scss/molecules/_progress-indicator.scss: -------------------------------------------------------------------------------- 1 | .progress-indicator { 2 | width: 10em; 3 | margin: 2em auto; 4 | text-align: center; 5 | position: relative; 6 | &:before { 7 | content: ''; 8 | display: block; 9 | position: absolute; 10 | top: 0; 11 | height: 6px; 12 | left: 0; 13 | right: 0; 14 | border-radius: 3px; 15 | background-color: $color-light-grey; 16 | z-index: -1; 17 | border: 1px solid $color-tan; 18 | } 19 | } 20 | 21 | .progress-indicator__bar { 22 | height: 6px; 23 | min-width: 6px; 24 | border-radius: 3px; 25 | background-color: $color-teal; 26 | } 27 | 28 | .progress-indicator__percentage { 29 | margin-top: .5em; 30 | font-size: $font-size-small; 31 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/molecules/_scroller.scss: -------------------------------------------------------------------------------- 1 | @keyframes scroller { 2 | 0% { 3 | opacity: 0; 4 | } 5 | 6 | 100% { 7 | opacity: 1; 8 | } 9 | 10 | } 11 | 12 | .scroller { 13 | animation: scroller 2s 1; 14 | position: absolute; 15 | left: 50%; 16 | bottom: 1em; 17 | margin-left: -1em; 18 | display: block; 19 | width: 2em; 20 | height: 2em; 21 | color: $color-darkest-grey; 22 | text-decoration: none; 23 | &:before { 24 | font-family: $font-icon; 25 | font-size: $font-size-h2; 26 | display: block; 27 | content: '\e313'; 28 | text-align: center; 29 | line-height: 1.5em; 30 | } 31 | } 32 | 33 | .scroller--light { 34 | color: #FFFFFF; 35 | &:hover { 36 | color: $color-magenta; 37 | } 38 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/molecules/_summary-table.scss: -------------------------------------------------------------------------------- 1 | .summary-table { 2 | text-align: center; 3 | .grid__item { 4 | margin-bottom: 1em; 5 | } 6 | } 7 | 8 | .summary-table--left { 9 | text-align: left; 10 | } 11 | 12 | .summary-table__label { 13 | @extend .text--help; 14 | margin-bottom: 0; 15 | } 16 | 17 | .summary-table__value { 18 | @extend .text--pullquote; 19 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/molecules/_toolbar.scss: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | @include clearfix(); 3 | max-width: $site-max-width; 4 | margin: { 5 | left: auto; 6 | right: auto; 7 | } 8 | } 9 | 10 | .toolbar__item { 11 | float: left; 12 | margin-right: 1em; 13 | } 14 | 15 | .toolbar__left { 16 | float: left; 17 | margin-right: .5em; 18 | } 19 | 20 | .toolbar__right { 21 | float: right; 22 | margin-left: .5em; 23 | .toolbar__item { 24 | margin-right: 0; 25 | margin-left: 1em; 26 | display: inline-block; 27 | float: left; 28 | } 29 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/neat/_neat-helpers.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | @import "mixins/clearfix"; 3 | 4 | // Functions 5 | @import "functions/private"; 6 | @import "functions/new-breakpoint"; 7 | 8 | // Settings 9 | @import "settings/grid"; 10 | @import "settings/visual-grid"; 11 | @import "settings/disable-warnings"; 12 | -------------------------------------------------------------------------------- /intake/static/intake/scss/neat/_neat.scss: -------------------------------------------------------------------------------- 1 | // Neat 1.8.0 2 | // http://neat.bourbon.io 3 | // Copyright 2012-2015 thoughtbot, inc. 4 | // MIT License 5 | 6 | // Helpers 7 | @import "neat-helpers"; 8 | 9 | // Grid 10 | @import "grid/private"; 11 | @import "grid/box-sizing"; 12 | @import "grid/omega"; 13 | @import "grid/outer-container"; 14 | @import "grid/span-columns"; 15 | @import "grid/row"; 16 | @import "grid/shift"; 17 | @import "grid/pad"; 18 | @import "grid/fill-parent"; 19 | @import "grid/media"; 20 | @import "grid/to-deprecate"; 21 | @import "grid/visual-grid"; 22 | @import "grid/display-context"; 23 | @import "grid/direction-context"; 24 | -------------------------------------------------------------------------------- /intake/static/intake/scss/neat/grid/_box-sizing.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @if $border-box-sizing == true { 4 | html { // http://bit.ly/1qk2tVR 5 | box-sizing: border-box; 6 | } 7 | 8 | * { 9 | &, 10 | &::after, 11 | &::before { 12 | box-sizing: inherit; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /intake/static/intake/scss/neat/grid/_fill-parent.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Forces the element to fill its parent container. 4 | /// 5 | /// @example scss - Usage 6 | /// .element { 7 | /// @include fill-parent; 8 | /// } 9 | /// 10 | /// @example css - CSS Output 11 | /// .element { 12 | /// width: 100%; 13 | /// box-sizing: border-box; 14 | /// } 15 | 16 | @mixin fill-parent() { 17 | width: 100%; 18 | 19 | @if $border-box-sizing == false { 20 | box-sizing: border-box; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /intake/static/intake/scss/neat/grid/_pad.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Adds padding to the element. 4 | /// 5 | /// @param {List} $padding [flex-gutter()] 6 | /// A list of padding value(s) to use. Passing `default` in the list will result in using the gutter width as a padding value. 7 | /// 8 | /// @example scss - Usage 9 | /// .element { 10 | /// @include pad(30px -20px 10px default); 11 | /// } 12 | /// 13 | /// @example css - CSS Output 14 | /// .element { 15 | /// padding: 30px -20px 10px 2.35765%; 16 | /// } 17 | 18 | @mixin pad($padding: flex-gutter()) { 19 | $padding-list: null; 20 | @each $value in $padding { 21 | $value: if($value == 'default', flex-gutter(), $value); 22 | $padding-list: join($padding-list, $value); 23 | } 24 | padding: $padding-list; 25 | } 26 | -------------------------------------------------------------------------------- /intake/static/intake/scss/neat/mixins/_clearfix.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Provides an easy way to include a clearfix for containing floats. 4 | /// 5 | /// @link http://goo.gl/yP5hiZ 6 | /// 7 | /// @example scss 8 | /// .element { 9 | /// @include clearfix; 10 | /// } 11 | /// 12 | /// @example css 13 | /// .element::after { 14 | /// clear: both; 15 | /// content: ""; 16 | /// display: block; 17 | /// } 18 | 19 | @mixin clearfix { 20 | &::after { 21 | clear: both; 22 | content: ""; 23 | display: block; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /intake/static/intake/scss/neat/settings/_disable-warnings.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /// Disable all deprecation warnings. Defaults to `false`. Set with a `!global` flag. 4 | /// 5 | /// @type Bool 6 | 7 | $disable-warnings: false !default; 8 | 9 | @mixin -neat-warn($message) { 10 | @if $disable-warnings == false { 11 | @warn "#{$message}"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /intake/static/intake/scss/organisms/_county-seal-list.scss: -------------------------------------------------------------------------------- 1 | .county-seal-list { 2 | text-align: center; 3 | max-width: 35em; 4 | margin: 0 auto; 5 | } 6 | .county-seal-list + p { 7 | text-align: center; 8 | } 9 | 10 | .county-seal { 11 | margin: 0 0.4em 0.4em 0.4em; 12 | display: inline-block; 13 | text-align: center; 14 | outline: 10px red !important; 15 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/organisms/_demo-banner.scss: -------------------------------------------------------------------------------- 1 | .demo-banner { 2 | @include full-bleed(); 3 | position: relative; 4 | z-index: 2; 5 | background-color: $color-red; 6 | padding: .5em; 7 | text-align: center; 8 | font-size: $font-size-small; 9 | color: #FFFFFF; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /intake/static/intake/scss/organisms/_legal.scss: -------------------------------------------------------------------------------- 1 | .legal { 2 | margin-top: -1em; 3 | margin-bottom: 2em; 4 | max-width: 30em; 5 | margin-left: auto; 6 | margin-right: auto; 7 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/organisms/_pagination.scss: -------------------------------------------------------------------------------- 1 | .pagination { 2 | @include clearfix; 3 | padding: 2em 0; 4 | 5 | .text--help { 6 | margin-top: .3em; 7 | } 8 | 9 | .button { 10 | min-width: 2.5em; 11 | text-align: center; 12 | } 13 | 14 | .pagination__selected { 15 | @extend .button--primary; 16 | } 17 | 18 | .pagination__ellipsis { 19 | display: inline-block; 20 | width: 2em; 21 | text-align: center; 22 | } 23 | 24 | @include media($tablet-up) { 25 | 26 | .pagination__info { 27 | float: left; 28 | } 29 | 30 | .pagination__buttons { 31 | float: right; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/organisms/_statistic-card.scss: -------------------------------------------------------------------------------- 1 | .statistic-card { 2 | @extend .card; 3 | } 4 | 5 | .statistic-card__label { 6 | @extend .text--help; 7 | margin-bottom: .5em; 8 | } 9 | 10 | .statistic-card__number { 11 | @extend .h1; 12 | margin-top: 0; 13 | } 14 | -------------------------------------------------------------------------------- /intake/static/intake/scss/organisms/_sunset-notice-alert.scss: -------------------------------------------------------------------------------- 1 | .sunset-notice-alert { 2 | background-color: #E5F2FD; 3 | padding: 15px 10px; 4 | &__icon { 5 | position: absolute; 6 | color: #1380D0; 7 | font-size: 32px; 8 | display: none; 9 | @include media($tablet-up) { 10 | display: block; 11 | } 12 | } 13 | 14 | &__details { 15 | margin-bottom: 0; 16 | margin-left: 0; 17 | 18 | @include media($tablet-up) { 19 | margin-left: 45px; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/templates/_banner.scss: -------------------------------------------------------------------------------- 1 | .banner { 2 | background-color: #E5F2FD; 3 | color: #423A3E; 4 | text-align: center; 5 | padding: 10px; 6 | margin-top: 2em; 7 | margin-left: inherit; 8 | margin-right: inherit; 9 | margin-bottom: 0; 10 | } -------------------------------------------------------------------------------- /intake/static/intake/scss/templates/_partnerships-contact.scss: -------------------------------------------------------------------------------- 1 | .partnerships-contact { 2 | .slab { 3 | border-top: 1px solid $color-tan; 4 | } 5 | } -------------------------------------------------------------------------------- /intake/static/voicemail/CMR_voicemail_greeting.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/static/voicemail/CMR_voicemail_greeting.mp3 -------------------------------------------------------------------------------- /intake/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/__init__.py -------------------------------------------------------------------------------- /intake/tests/factories/applicant_factory.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from intake import models 3 | from .visitor_factory import VisitorFactory 4 | 5 | 6 | class ApplicantFactory(factory.DjangoModelFactory): 7 | visitor = factory.SubFactory(VisitorFactory) 8 | 9 | class Meta: 10 | model = models.Applicant 11 | -------------------------------------------------------------------------------- /intake/tests/factories/county_factory.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from intake import models 3 | 4 | 5 | class CountyFactory(factory.DjangoModelFactory): 6 | slug = factory.Sequence(lambda n: 'county-{}'.format(n)) 7 | name = factory.Sequence(lambda n: 'Fake County {}'.format(n)) 8 | description = factory.Sequence( 9 | lambda n: ( 10 | 'Fake County {} (near Tlön, Uqbar, or Orbis Tertius)'.format( 11 | n))) 12 | 13 | class Meta: 14 | model = models.County 15 | -------------------------------------------------------------------------------- /intake/tests/factories/prebuilt_pdf_bundle_factory.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from intake import models 3 | from user_accounts.models import Organization 4 | 5 | 6 | def get_sf_pubdef(*args, **kwargs): 7 | return Organization.objects.get(slug="sf_pubdef") 8 | 9 | 10 | class PrebuiltPDFBundleFactory(factory.DjangoModelFactory): 11 | organization = factory.LazyFunction(get_sf_pubdef) 12 | 13 | class Meta: 14 | model = models.PrebuiltPDFBundle 15 | -------------------------------------------------------------------------------- /intake/tests/factories/status_notification_factory.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from intake import models 3 | 4 | 5 | class StatusNotificationFactory(factory.DjangoModelFactory): 6 | status_update = factory.Iterator(models.StatusUpdate.objects.all()) 7 | contact_info = {"email": "cmrtestuser+notification@gmail.com"} 8 | base_message = "Resistance is futile" 9 | sent_message = "Live long and prosper" 10 | 11 | class Meta: 12 | model = models.StatusNotification 13 | -------------------------------------------------------------------------------- /intake/tests/factories/status_type.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from intake import models 3 | 4 | 5 | class StatusTypeFactory(factory.DjangoModelFactory): 6 | label = 'Everything is Awesome' 7 | display_name = 'Awesome' 8 | template = 'Dear {{first_name}}, your case is just fantastic!' 9 | help_text = 'Client has nothing to worry about' 10 | slug = 'everything-is-awesome' 11 | 12 | class Meta: 13 | model = models.StatusType 14 | -------------------------------------------------------------------------------- /intake/tests/factories/submission_tag_link_factory.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from intake import models 3 | from user_accounts.tests.factories import UserFactory 4 | from .tag_factory import TagFactory 5 | from .form_submission_factory import FormSubmissionFactory 6 | 7 | 8 | class SubmissionTagLinkFactory(factory.DjangoModelFactory): 9 | content_object = factory.SubFactory(FormSubmissionFactory) 10 | tag = factory.SubFactory(TagFactory) 11 | user = factory.SubFactory(UserFactory) 12 | 13 | class Meta: 14 | model = models.SubmissionTagLink 15 | -------------------------------------------------------------------------------- /intake/tests/factories/tag_factory.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from taggit import models 3 | 4 | 5 | class TagFactory(factory.DjangoModelFactory): 6 | name = factory.Sequence(lambda n: 'tag-{}'.format(n)) 7 | 8 | class Meta: 9 | model = models.Tag 10 | -------------------------------------------------------------------------------- /intake/tests/factories/utils.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/factories/utils.py -------------------------------------------------------------------------------- /intake/tests/factories/visitor_factory.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from intake import models 3 | 4 | 5 | class VisitorFactory(factory.DjangoModelFactory): 6 | source = 'share' 7 | referrer = 'http://bajoradefender.org/services/clean-slate/' 8 | ip_address = '48.104.186.127' 9 | 10 | class Meta: 11 | model = models.Visitor 12 | -------------------------------------------------------------------------------- /intake/tests/forms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/forms/__init__.py -------------------------------------------------------------------------------- /intake/tests/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/management/__init__.py -------------------------------------------------------------------------------- /intake/tests/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/management/commands/__init__.py -------------------------------------------------------------------------------- /intake/tests/mock_referrers.py: -------------------------------------------------------------------------------- 1 | choices = [ 2 | None, 3 | 'https://t.co/8syieoGr9k', 4 | 'https://checkrapplicant.zendesk.com/hc/en-us', 5 | 'http://sfpublicdefender.org/', 6 | 'http://sfpublicdefender.org/services/clean-slate/', 7 | 'http://www.safeandjust.org/county-map', 8 | 'https://www.facebook.com/', 9 | 'https://m.facebook.com/', 10 | 'https://www.google.com/', 11 | 'https://www.codeforamerica.org/products/clear-my-record' 12 | ] 13 | -------------------------------------------------------------------------------- /intake/tests/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/models/__init__.py -------------------------------------------------------------------------------- /intake/tests/models/test_application_event.py: -------------------------------------------------------------------------------- 1 | # from unittest.mock import patch 2 | from django.test import TestCase 3 | 4 | # from intake import models 5 | # from intake.tests import factories 6 | 7 | 8 | class TestApplicationEvent(TestCase): 9 | 10 | def test_nothing(self): 11 | pass 12 | -------------------------------------------------------------------------------- /intake/tests/models/test_status_notification.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from intake import models 3 | 4 | 5 | class TestStatusNotification(TestCase): 6 | pass 7 | -------------------------------------------------------------------------------- /intake/tests/models/test_status_update.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | 4 | class TestStatusUpdate(TestCase): 5 | pass 6 | -------------------------------------------------------------------------------- /intake/tests/models/test_visitor.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from intake.models import Visitor 4 | 5 | 6 | class TestVisitor(TestCase): 7 | 8 | def test_init_visitor_with_no_info(self): 9 | visitor = Visitor() 10 | visitor.save() 11 | self.assertTrue(visitor.id) 12 | self.assertTrue(visitor.first_visit) 13 | 14 | def test_save_with_source_and_referrer(self): 15 | visitor = Visitor( 16 | source='prop47fair', 17 | referrer='www.google.com') 18 | visitor.save() 19 | self.assertTrue(visitor.id) 20 | self.assertTrue(visitor.first_visit) 21 | -------------------------------------------------------------------------------- /intake/tests/serializers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/serializers/__init__.py -------------------------------------------------------------------------------- /intake/tests/service_objects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/service_objects/__init__.py -------------------------------------------------------------------------------- /intake/tests/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/services/__init__.py -------------------------------------------------------------------------------- /intake/tests/test_commands.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock, patch 2 | from django.test import TestCase 3 | from intake.management import commands 4 | 5 | 6 | class TestCommands(TestCase): 7 | 8 | @patch( 9 | 'intake.management.commands.send_unopened_apps_notification' 10 | '.BundlesService') 11 | def test_send_unopened_apps_notification(self, BundlesService): 12 | command = Mock() 13 | commands.send_unopened_apps_notification.Command.handle(command) 14 | BundlesService.count_unreads_and_send_notifications_to_orgs\ 15 | .assert_called_once_with() 16 | command.style.SUCCESS.assert_called_once_with( 17 | "Successfully referred any unopened apps") 18 | -------------------------------------------------------------------------------- /intake/tests/test_fields.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from formation import fields 3 | 4 | 5 | class TestHouseholdSizeField(TestCase): 6 | 7 | def test_display_value_is_larger_than_input(self): 8 | data = {'household_size': 2} 9 | field = fields.HouseholdSize(data) 10 | self.assertTrue(field.is_valid()) 11 | self.assertEqual(field.get_current_value(), 2) 12 | self.assertEqual(field.get_display_value(), 3) 13 | -------------------------------------------------------------------------------- /intake/tests/test_groups.py: -------------------------------------------------------------------------------- 1 | from intake.tests.base_testcases import IntakeDataTestCase 2 | from django.contrib.auth.models import Group 3 | from intake import groups 4 | 5 | 6 | class TestGroups(IntakeDataTestCase): 7 | 8 | fixtures = [ 9 | 'counties', 'organizations', 'groups', 'mock_profiles' 10 | ] 11 | 12 | def test_followup_staff_group(self): 13 | user = self.be_cfa_user() 14 | group = Group.objects.get(name=groups.FOLLOWUP_STAFF) 15 | self.assertIn(group, user.groups.all()) 16 | 17 | def test_application_reviewers_group(self): 18 | user = self.be_cfa_user() 19 | group = Group.objects.get(name=groups.APPLICATION_REVIEWERS) 20 | self.assertIn(group, user.groups.all()) 21 | -------------------------------------------------------------------------------- /intake/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from unittest.mock import Mock, patch, MagicMock 3 | 4 | 5 | class TestContextProcessors(TestCase): 6 | 7 | def test_oxford_comma(self): 8 | from project.jinja2 import oxford_comma 9 | items = ["apples", "oranges", "bananas"] 10 | expected_phrases = [ 11 | "apples, oranges, and bananas", 12 | "apples and oranges", 13 | "apples", 14 | ] 15 | for phrase in expected_phrases: 16 | self.assertEqual( 17 | phrase, oxford_comma(items)) 18 | items.pop() 19 | -------------------------------------------------------------------------------- /intake/tests/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/views/__init__.py -------------------------------------------------------------------------------- /intake/tests/views/test_newapps_pdf_view.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/tests/views/test_newapps_pdf_view.py -------------------------------------------------------------------------------- /intake/translators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/translators/__init__.py -------------------------------------------------------------------------------- /intake/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/intake/views/__init__.py -------------------------------------------------------------------------------- /intake/views/stats_views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic.base import TemplateView 2 | from intake.services import statistics 3 | 4 | 5 | class Stats(TemplateView): 6 | """A view that shows a public summary of service performance. 7 | """ 8 | template_name = "stats.jinja" 9 | 10 | def get_context_data(self, **kwargs): 11 | context = super().get_context_data(**kwargs) 12 | context['stats'] = {'org_stats': statistics.get_org_data_dict()} 13 | return context 14 | 15 | 16 | stats = Stats.as_view() 17 | -------------------------------------------------------------------------------- /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", "local_settings") # Only used in dev 7 | from django.core.management import execute_from_command_line 8 | execute_from_command_line(sys.argv) 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "bootstrap": "^3.4.1", 5 | "browserify": "^16.5.2", 6 | "c3": "^0.4.23", 7 | "cached-path-relative": ">=1.0.2", 8 | "jquery": "^3.5.0", 9 | "less": "^3.11.0", 10 | "moment": "~> 2.19.3", 11 | "vinyl-buffer": "^1.0.1", 12 | "vinyl-source-stream": "^1.1.2", 13 | "bl": "^1.2.3", 14 | "elliptic": ">=6.5.4", 15 | "path-parse": ">=1.0.7" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /partnerships/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/partnerships/__init__.py -------------------------------------------------------------------------------- /partnerships/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/partnerships/migrations/__init__.py -------------------------------------------------------------------------------- /partnerships/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/partnerships/tests/__init__.py -------------------------------------------------------------------------------- /partnerships/tests/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from partnerships import models 3 | from intake.tests.factories import VisitorFactory 4 | 5 | 6 | class PartnershipLeadFactory(factory.DjangoModelFactory): 7 | visitor = factory.SubFactory(VisitorFactory) 8 | name = 'Ziggy Stardust' 9 | email = 'ziggy@mars.space' 10 | organization_name = 'Spiders from Mars' 11 | message = 'Jamming good with Weird and Gilly' 12 | 13 | class Meta: 14 | model = models.PartnershipLead 15 | -------------------------------------------------------------------------------- /partnerships/urls.py: -------------------------------------------------------------------------------- 1 | from django2_url_robots.utils import url 2 | from .views import contact, home 3 | 4 | 5 | urlpatterns = [ 6 | url(r'^$', home, name='partnerships-home', robots_allow=True), 7 | url(r'^get-in-touch$', 8 | contact, name='partnerships-contact', robots_allow=True), 9 | ] 10 | -------------------------------------------------------------------------------- /phone/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/phone/__init__.py -------------------------------------------------------------------------------- /phone/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PhoneConfig(AppConfig): 5 | name = 'phone' 6 | -------------------------------------------------------------------------------- /phone/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/phone/tests/__init__.py -------------------------------------------------------------------------------- /phone/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from phone.views import record, handle_new_message 3 | 4 | urlpatterns = [ 5 | # voicemail views 6 | # these views are public, csrf exempt, and answer to POST data 7 | url(r'^handle-incoming-call$', record, name='phone-handle_incoming_call'), 8 | url(r'^handle-new-message$', 9 | handle_new_message, name='phone-handle_new_message'), 10 | ] 11 | -------------------------------------------------------------------------------- /phone/validators.py: -------------------------------------------------------------------------------- 1 | from twilio.request_validator import RequestValidator 2 | from django.conf import settings 3 | 4 | 5 | def is_valid_twilio_request(request): 6 | twilio_request_validator = RequestValidator(settings.TWILIO_AUTH_TOKEN) 7 | request_path = request.build_absolute_uri( 8 | request.get_full_path()).replace('http:', 'https:') 9 | # trailing slashes should be removed 10 | if request_path[-1] == '/': 11 | request_path = request_path[:-1] 12 | twilio_signature = request.META.get('HTTP_X_TWILIO_SIGNATURE', '') 13 | return twilio_request_validator.validate( 14 | request_path, request.POST, twilio_signature) 15 | -------------------------------------------------------------------------------- /printing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/printing/__init__.py -------------------------------------------------------------------------------- /project/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | 3 | __all__ = ['celery_app'] 4 | -------------------------------------------------------------------------------- /project/alerts.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from intake import tasks 3 | 4 | 5 | def send_email_to_admins(subject, message): 6 | """Asynchronously sends an email alert to all ADMINs in settings 7 | """ 8 | tasks.send_email.delay( 9 | subject=subject, message=message, 10 | from_email=settings.MAIL_DEFAULT_SENDER, 11 | recipient_list=[email for name, email in settings.ADMINS]) 12 | -------------------------------------------------------------------------------- /project/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | from celery import Celery 3 | 4 | # set the default Django settings module for the 'celery' program. 5 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'local_settings') 6 | 7 | app = Celery('intake') 8 | 9 | # Using a string here means the worker don't have to serialize 10 | # the configuration object to child processes. 11 | # - namespace='CELERY' means all celery-related configuration keys 12 | # should have a `CELERY_` prefix. 13 | app.config_from_object('django.conf:settings', namespace='CELERY') 14 | 15 | # Load task modules from all registered Django app configs. 16 | app.autodiscover_tasks() 17 | 18 | 19 | @app.task(bind=True) 20 | def debug_task(self): 21 | print('Request: {0!r}'.format(self.request)) 22 | -------------------------------------------------------------------------------- /project/decorators.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from functools import wraps 3 | from django.conf import settings 4 | 5 | 6 | # Get an instance of a logger 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def run_if_setting_true(setting_name, alternate_return_value): 11 | def true_decorator(func): 12 | @wraps(func) 13 | def wrapped(*args, **kwargs): 14 | if not getattr(settings, setting_name, False): 15 | logger.info( 16 | "{} caused skipped function call {}". 17 | format(setting_name, func.__name__)) 18 | return alternate_return_value 19 | return func(*args, **kwargs) 20 | return wrapped 21 | return true_decorator 22 | -------------------------------------------------------------------------------- /project/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | class InvalidQueryParamsError(Exception): 3 | pass 4 | -------------------------------------------------------------------------------- /project/heroku_wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.core.wsgi import get_wsgi_application 3 | 4 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings.prod") 5 | 6 | application = get_wsgi_application() 7 | -------------------------------------------------------------------------------- /project/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/project/services/__init__.py -------------------------------------------------------------------------------- /project/services/mixpanel_service.py: -------------------------------------------------------------------------------- 1 | from mixpanel import Mixpanel 2 | from . import logging_service 3 | from django.conf import settings 4 | 5 | 6 | _mixpanel_client = None 7 | 8 | 9 | def get_mixpanel_client(): 10 | # Why this? seems like there must be a simpler way 11 | global _mixpanel_client 12 | if _mixpanel_client is None: 13 | mixpanel_key = getattr(settings, 'MIXPANEL_KEY', None) 14 | if mixpanel_key: 15 | _mixpanel_client = Mixpanel(mixpanel_key) 16 | return _mixpanel_client 17 | -------------------------------------------------------------------------------- /project/services/query_params.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from ..exceptions import InvalidQueryParamsError 3 | 4 | 5 | def get_ids_from_query_params(request, key='ids', sep=','): 6 | string_value = request.GET.get(key, '') 7 | str_ids = string_value.split(sep) 8 | try: 9 | ids = [int(str_id) for str_id in str_ids] 10 | except ValueError as err: 11 | raise InvalidQueryParamsError( 12 | "Received invalid query params: {url}".format( 13 | url=request.get_full_path())) 14 | return ids 15 | 16 | 17 | def get_url_for_ids(view_name, ids, key='ids'): 18 | url = reverse(view_name) 19 | params = '?' + key + '=' + ','.join(sorted([str(i) for i in ids])) 20 | return url + params 21 | -------------------------------------------------------------------------------- /project/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/project/settings/__init__.py -------------------------------------------------------------------------------- /project/settings/development.py: -------------------------------------------------------------------------------- 1 | # This file intentionally left blank. 2 | # Developers maintain their own uncommitted `local_settings.py` files in the project root 3 | # For an example of a `local_settings.py` file, see `local_settings.py.example` 4 | -------------------------------------------------------------------------------- /project/settings/review.py: -------------------------------------------------------------------------------- 1 | import dj_database_url 2 | from project.settings.deployed import * 3 | 4 | # looks for 'DATABASE_URL' environmental variable 5 | DATABASES = { 6 | 'default': dj_database_url.config(ssl_require=True), 7 | 'purged': dj_database_url.config(ssl_require=True), 8 | } 9 | 10 | # Settings for file uploads 11 | AWS_ACCESS_KEY_ID = os.environ.get('BUCKETEER_AWS_ACCESS_KEY_ID') 12 | AWS_SECRET_ACCESS_KEY = os.environ.get('BUCKETEER_AWS_SECRET_ACCESS_KEY') 13 | AWS_STORAGE_BUCKET_NAME = os.environ.get('BUCKETEER_BUCKET_NAME') 14 | -------------------------------------------------------------------------------- /project/settings/test.py: -------------------------------------------------------------------------------- 1 | DIVERT_REMOTE_CONNECTIONS = True 2 | ALLOW_REQUESTS_TO_MAILGUN = False 3 | VOICEMAIL_NOTIFICATION_EMAIL = "test@example.com" 4 | -------------------------------------------------------------------------------- /project/templates/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /* # disallow all 3 | {{ rules|safe }} 4 | -------------------------------------------------------------------------------- /project/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/project/tests/__init__.py -------------------------------------------------------------------------------- /project/tests/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/project/tests/services/__init__.py -------------------------------------------------------------------------------- /project/tests/test_decorators.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from project.decorators import run_if_setting_true 4 | 5 | 6 | class TestRunIfSettingTrue(TestCase): 7 | 8 | @run_if_setting_true("MY_SETTING", "foo") 9 | def sample_function(self): 10 | return "bar" 11 | 12 | def test_with_setting_undefined(self): 13 | self.assertEqual(self.sample_function(), "foo") 14 | 15 | def test_with_setting_true(self): 16 | with self.settings(MY_SETTING=True): 17 | self.assertEqual(self.sample_function(), "bar") 18 | 19 | def test_with_setting_false(self): 20 | with self.settings(MY_SETTING=False): 21 | self.assertEqual(self.sample_function(), "foo") 22 | -------------------------------------------------------------------------------- /project/tests/utils.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def login(client, profile): 5 | client.login( 6 | username=profile.user.username, 7 | password=settings.TEST_USER_PASSWORD) 8 | -------------------------------------------------------------------------------- /project/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | application = get_wsgi_application() 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Included because many PaaS require a requirements.txt file 2 | # in the project root. 3 | 4 | -r requirements/prod.txt 5 | -------------------------------------------------------------------------------- /requirements/ci.txt: -------------------------------------------------------------------------------- 1 | # Dependencies for automated testing via CircleCI 2 | -r prod.txt 3 | -r dev.txt -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | # Requirements for development and testing 2 | -r app.txt 3 | 4 | xlrd==1.2.0 5 | autopep8~=1.5 6 | pep8~=1.7 7 | coverage~=5.0.3 8 | codeclimate-test-reporter~=0.2 9 | beautifulsoup4==4.8.2 10 | selenium~=3.141.0 11 | behave-django~=1.3 12 | -------------------------------------------------------------------------------- /requirements/prod.txt: -------------------------------------------------------------------------------- 1 | # Requirements for production 2 | -r app.txt 3 | 4 | sentry-sdk==0.14.3 5 | gunicorn==20.0.4 6 | dj-database-url==0.5.0 7 | django-celery-beat==2.0.0 8 | boto==2.49.0 9 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.6.10 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pycodestyle] 2 | exclude = migrations,lib,bin,node_modules,mock_serialized_apps.py,mock_user_agents.py,local_settings.py,src,notes,venv36 3 | max-line-length = 120 4 | ignore = W606,E123,E121,E126,W504 5 | -------------------------------------------------------------------------------- /system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 # Default 2 | -------------------------------------------------------------------------------- /templates/admin_base_single_column.jinja: -------------------------------------------------------------------------------- 1 | {% extends "cmr_base.jinja" %} 2 | 3 | {%- block head_title -%} 4 | Clear My Record 5 | {%- endblock head_title -%} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
12 |

13 | {%- block header_title -%} 14 | {%- endblock header_title -%} 15 |

16 | {% block column_content %} 17 | {% endblock column_content %} 18 |
19 |
20 |
21 |
22 | {% endblock content %} 23 | -------------------------------------------------------------------------------- /templates/admin_form_base.jinja: -------------------------------------------------------------------------------- 1 | {% extends "admin_base_single_column.jinja" %} 2 | {%- block section_class %}form{% endblock section_class -%} 3 | {% block column_content %} 4 | {% block form %} 5 | {% endblock form %} 6 | {% endblock column_content %} -------------------------------------------------------------------------------- /templates/app_bundle_action_bar.jinja: -------------------------------------------------------------------------------- 1 | {#- 2 | This displays the set of actions relating to an application bundle 3 | that are available to a user. 4 | 5 | In the future, the set of actions should be determined through more detailed 6 | permissions. But for now the actions will be hardcoded 7 | 8 | context: 9 | bundle - an ApplicationBundle 10 | 11 | #} 12 | 13 | 14 | Get a printout of these cases 15 | 16 | -------------------------------------------------------------------------------- /templates/app_history.jinja: -------------------------------------------------------------------------------- 1 | {% extends "app_detail.jinja" %} 2 | 3 | {%- block page_heading -%} 4 | Application History 5 | {%- endblock page_heading -%} 6 | 7 |
8 |
9 | {%- block app_detail_contents -%} 10 |
    11 | {%- if status_updates %} 12 | {% for status_update in status_updates %} 13 | {% include "includes/application_event.jinja" %} 14 | {% endfor %} 15 | {%- else %} 16 |

    New (no status updates yet)

    17 | {%- endif %} 18 |
19 | {%- endblock app_detail_contents -%} 20 |
21 |
-------------------------------------------------------------------------------- /templates/app_reviewer_list.jinja: -------------------------------------------------------------------------------- 1 | {%- if no_results %} 2 |

{{ no_results }}

3 | {%- else %} 4 | 5 | 6 | 7 | 8 | 9 | {%- if show_pdf %} 10 | 11 | {%- endif %} 12 | 13 | 14 | 15 | 16 | 17 | {%- for app in results %} 18 | {%- if app.was_transferred_out %} 19 | {%- include "includes/org_user_transferred_app_listing.jinja" %} 20 | {%- else %} 21 | {%- include "includes/org_user_app_index_listing.jinja" %} 22 | {%- endif %} 23 | {%- endfor %} 24 |
DateLast NameFirst NameIntake PDFStatusLatest Status UpdateActions
25 | {%- endif %} -------------------------------------------------------------------------------- /templates/cmr_base.jinja: -------------------------------------------------------------------------------- 1 | {% extends "base.jinja" %} 2 | 3 | {% block css %} 4 | {% compress 'css' %} 5 | 6 | {% endcompress %} 7 | {% endblock css %} 8 | 9 | {% block body %} 10 | {% include "includes/header.jinja" %} 11 | {% block main %} 12 | {% block content %} 13 | {% endblock content %} 14 | {% endblock main %} 15 | {% endblock body %} 16 | -------------------------------------------------------------------------------- /templates/create_status_update.jinja: -------------------------------------------------------------------------------- 1 | {% extends "admin_form_base.jinja" %} 2 | {% import 'macros.jinja' as macros %} 3 | 4 | {%- block header_title -%} 5 | Update Application Status for {{ submission.get_full_name() }} 6 | {%- endblock header_title -%} 7 | 8 | {% block form %} 9 | 10 | {% include "includes/minimal_latest_status_line.jinja" %} 11 | 12 |
13 | {% include "includes/csrf_field.jinja" %} 14 | {{ macros.render_form_fields(form) }} 15 |

16 | 17 |

18 |
19 | {% endblock form %} -------------------------------------------------------------------------------- /templates/email/app_bundle_email.jinja: -------------------------------------------------------------------------------- 1 | As of {{ current_local_time('%-I:%M %p %Z on %A %b %-d') }}, {{ org_name }} has 2 | {%- if unread_count == 1 %} one unread application{% else %} {{ unread_count }} unread applications 3 | {%- endif %} to Clear My Record. 4 | 5 | You can review and print them at this link: 6 | {{ unread_redirect_link }} 7 | 8 | There are also {{ update_count }} applications awaiting a status update. You can review them at this link: 9 | {{ needs_update_redirect_link }} 10 | 11 | You may also review all {{ all_count }} applications at this link: 12 | {{ all_redirect_link }} 13 | 14 | If you have any questions or concerns, email us at clearmyrecord@codeforamerica.org and we will get back to you right away. 15 | 16 | Best, 17 | 18 | Clear My Record team 19 | clearmyrecord@codeforamerica.org 20 | -------------------------------------------------------------------------------- /templates/email/applicant_edit_notification.jinja: -------------------------------------------------------------------------------- 1 | Hello, 2 | 3 | Your application has been updated by {{org_name}}. 4 | Here is the information that was changed: 5 | 6 | {%- for field in changed_fields %} 7 | - {{ field }} 8 | {%- endfor %} 9 | 10 | You are receiving this email because Clear My Record has an application on file with this email address. 11 | {%- if is_old_contact_info %} 12 | This is the last email you will get from us at this email address. 13 | {%- endif %} 14 | 15 | We value your privacy. If you did not approve this change or would like more information, reply to this email to contact {{ org_name }}. 16 | -------------------------------------------------------------------------------- /templates/email/confirmation.jinja: -------------------------------------------------------------------------------- 1 | Hi {{ name|safe }}, 2 | 3 | Thanks for applying online to Clear My Record. 4 | {%- if organizations %} Since you applied for help in {{ oxford_comma(county_names)|safe }}, we are sending your application to {{ oxford_comma(organizations)|safe }}.{% endif %} 5 | {{ next_steps|join(' ')|safe }} 6 | {% if unlisted_counties %} 7 | Next steps for {{ unlisted_counties|safe }}: 8 | We will contact you in the next week with information on how to clear your record in {{ unlisted_counties|safe }} 9 | {%- endif %} 10 | 11 | Take care, 12 | {{ staff_name|safe }} at Clear My Record -------------------------------------------------------------------------------- /templates/email/followup.jinja: -------------------------------------------------------------------------------- 1 | Hello again {{ name|safe }}, 2 | 3 | This is {{ staff_name|safe }} from Clear My Record. You applied online about one month ago for help in {{ counties_applied_to|safe }} and we sent your application to {{ oxford_comma(organization_names)|safe }}. I'm just checking to see how things are going. 4 | 5 | {% for followup_message in followup_messages -%} 6 | {{ followup_message|safe }} 7 | {% endfor %} 8 | Best, 9 | 10 | {{ staff_name|safe }} at Clear My Record -------------------------------------------------------------------------------- /templates/email/org_edit_notification.jinja: -------------------------------------------------------------------------------- 1 | The information for applicant {{ applicant_name }} has been edited by {{ editor_email }} from {{ editor_org_name }}. You may want to update your organization's records. 2 | 3 | 4 | {%- for field, values in safe_data_diff.items() %} 5 | 6 | ------------ 7 | Updated {{ field }} 8 | 9 | Before: 10 | {{ (values['before'] or "(Blank)") }} 11 | 12 | After: 13 | {{ (values['after'] or "(Blank)") }} 14 | 15 | {%- endfor %} 16 | {%- for field in unsafe_changed_keys %} 17 | 18 | ------------ 19 | Updated {{ field }} 20 | 21 | {% endfor %} 22 | 23 | You can view {{ applicant_name }}'s full application here: 24 | {{ app_detail_url }} 25 | 26 | 27 | If you have any questions, please feel free to email us at: 28 | clearmyrecord@codeforamerica.org 29 | -------------------------------------------------------------------------------- /templates/followup_list.jinja: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {%- if perms.intake.view_application_note %} 9 | 10 | 11 | {%- endif %} 12 | 13 | {% include "followup_list_rows.jinja" %} 14 |
IDReceivedNameContactOrgs & StatusesNotesTags
-------------------------------------------------------------------------------- /templates/followup_list_rows.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | expects a 'results' to be data from FormSubmissionFollowupListSerializer 3 | expects humanize, perms 4 | -#} 5 | {% for sub in results %} 6 | {%- include "includes/followup_list_row.jinja" %} 7 | {%- endfor %} -------------------------------------------------------------------------------- /templates/forms/county_selection.jinja: -------------------------------------------------------------------------------- 1 | {% extends "forms/county_form.jinja" %} 2 | 3 | {% block county_scope %} 4 | {% endblock county_scope %} 5 | 6 | {% block form_content %} 7 | 8 | {{ form.render() }} 9 | 10 | {% endblock form_content %} -------------------------------------------------------------------------------- /templates/forms/declaration_letter_form.jinja: -------------------------------------------------------------------------------- 1 | {% extends "forms/county_form.jinja" %} 2 | 3 | 4 | {%- block header_title -%} 5 | Create Your Letter 6 | {%- endblock header_title -%} 7 | 8 | 9 | {%- block submit_button_label -%} 10 | Review your letter 11 | {%- endblock submit_button_label -%} -------------------------------------------------------------------------------- /templates/gcf-style-flash-messages.jinja: -------------------------------------------------------------------------------- 1 | {#- 2 | this include is intended to appear before the header or any other content 3 | -#} 4 |
5 | {%- if messages -%} 6 | {%- for message in messages -%} 7 |
8 |
9 |
10 |

11 | {% if 'safe' in message.tags %} 12 | {{- message|safe -}} 13 | {% else %} 14 | {{- message -}} 15 | {% endif %} 16 |

17 |
18 |
19 |
20 | {%- endfor %} 21 | {%- endif %} 22 |
23 | -------------------------------------------------------------------------------- /templates/gcf-style-public-header.jinja: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 8 |

9 |
10 |
11 |
-------------------------------------------------------------------------------- /templates/gcf_style_base.jinja: -------------------------------------------------------------------------------- 1 | {% extends "base.jinja" %} 2 | 3 | {% block css %} 4 | {% compress 'css' %} 5 | 6 | {% endcompress %} 7 | {% endblock css %} 8 | 9 | 10 | {% block body_class %}cfa-style{% endblock body_class %} 11 | 12 | {% block body %} 13 |
14 | {% include "gcf-style-flash-messages.jinja" %} 15 | {% include "gcf-style-public-header.jinja" %} 16 | 17 | {% block content %} 18 | {% endblock content %} 19 | 20 | {% include "gcf-style-footer.jinja" %} 21 |
22 | {% endblock body %} -------------------------------------------------------------------------------- /templates/includes/application_next_steps.jinja: -------------------------------------------------------------------------------- 1 |
2 | {%- for organization in organizations %} 3 |

4 | {%- if organization.county.slug != 'not_listed' %} 5 | To help you with {{ organization.county.name }} County, we sent your application to the 6 | {{ organization.name }}. 7 | {{ linkify(organization.long_confirmation_message) }} 8 | {% endif %} 9 |

10 | {%- endfor %} 11 | {%- if unlisted_counties %} 12 |

13 | We will contact you in the next week with information on how to clear your record in {{ unlisted_counties }}. 14 |

15 | {% endif %} 16 |
-------------------------------------------------------------------------------- /templates/includes/auth_bar.jinja: -------------------------------------------------------------------------------- 1 | {% if request.user.is_authenticated %} 2 | 9 | {% endif %} -------------------------------------------------------------------------------- /templates/includes/back_to_home_button.jinja: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /templates/includes/cmr_contact_info.jinja: -------------------------------------------------------------------------------- 1 |

2 | {{ _("If you have any questions or concerns, please email us at")}} 3 | {{ linkify(content.cmr_email) }} 4 | {{ _("or text us at")}} 5 | {{ linkify(content.cmr_phone_number) }} 6 |

-------------------------------------------------------------------------------- /templates/includes/county_list.jinja: -------------------------------------------------------------------------------- 1 | {%- if county_list %} 2 |
3 |
4 |
5 |
6 |

7 | 8 | {{ _("You are applying for help in ") -}} 9 | {{- oxford_comma(county_list) }}. {{ _("Switch counties") }}. 10 | 11 |

12 |
13 |
14 |
15 |
16 | {%- endif %} -------------------------------------------------------------------------------- /templates/includes/csrf_field.jinja: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /templates/includes/empty_status_summary.jinja: -------------------------------------------------------------------------------- 1 |
2 |
3 | Current Status: 4 |
5 | New 6 |
7 |
-------------------------------------------------------------------------------- /templates/includes/flash_messages.jinja: -------------------------------------------------------------------------------- 1 | 2 | {% block flash_messages %} 3 |
4 | {%- if messages -%} 5 | {%- for message in messages -%} 6 |
7 |
8 |
9 |
10 | 11 | {{ message }} 12 | 13 |
14 |
15 |
16 |
17 | {%- endfor %} 18 | {%- endif %} 19 |
20 | {% endblock %} -------------------------------------------------------------------------------- /templates/includes/google_analytics.jinja: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /templates/includes/header.jinja: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

7 | Clear My Record 8 |

9 | {% include "includes/auth_bar.jinja" %} 10 |
11 |
12 |
13 | 14 | {% include "includes/flash_messages.jinja" %} 15 |
16 | -------------------------------------------------------------------------------- /templates/includes/latest_status_summary_for_incoming_transfer.jinja: -------------------------------------------------------------------------------- 1 |
2 |
3 | Current Status: 4 |
5 | Transferred from {{ 6 | transfer.status_update.application.organization.name 7 | }} 8 | by {{transfer.status_update.author.profile.name}} 9 |
10 |
11 | Reason: 12 |
13 | {{transfer.reason}} 14 |
15 |
-------------------------------------------------------------------------------- /templates/includes/latest_status_summary_for_outgoing_transfer.jinja: -------------------------------------------------------------------------------- 1 |
2 |
3 | Current Status: 4 |
5 | {{application.latest_status.status_type.display_name}} to {{ 6 | application.latest_status.transfer.new_application.organization.name 7 | }} by {{application.latest_status.author.profile.name}} 8 |
9 |
10 | Reason: 11 |
12 | {{application.latest_status.transfer.reason}} 13 |
14 |
-------------------------------------------------------------------------------- /templates/includes/minimal_latest_status_line.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | assumes 'application' is in the context 3 | -#} 4 |

5 | {%- if application.latest_status %} 6 | Current status: 7 | 10 | {{ application.latest_status.status_type.display_name }} 11 | ({{ humanize.naturaltime(application.latest_status.created) }} 12 | by {{ application.latest_status.author.profile.name }}) 13 | 14 | {%- else %} 15 | New (no status updates yet) 16 | {%- endif %} 17 |

18 | -------------------------------------------------------------------------------- /templates/includes/public_flash_messages.jinja: -------------------------------------------------------------------------------- 1 | 2 | {% block flash_messages %} 3 |
4 | {%- if messages -%} 5 | {%- for message in messages -%} 6 |
7 |
8 |
9 |
10 | 11 | {{ message }} 12 | 13 |
14 |
15 |
16 |
17 | {%- endfor %} 18 | {%- endif %} 19 |
20 | {% endblock %} -------------------------------------------------------------------------------- /templates/includes/public_topbar.jinja: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | {{ content.topbar }} 6 | 7 |
8 |
-------------------------------------------------------------------------------- /templates/includes/submission_display_actions.jinja: -------------------------------------------------------------------------------- 1 | {%- if form.submission and request %} 2 | 15 | {%- endif %} -------------------------------------------------------------------------------- /templates/newapps_pdf_detail.jinja: -------------------------------------------------------------------------------- 1 | {% extends "cmr_base.jinja" %} 2 | 3 | {% block content %} 4 | 5 |
6 |
7 | 8 |
9 | 14 |
15 | 16 |
17 |
18 | 21 |
22 |
23 |
24 |
25 | {% endblock content %} -------------------------------------------------------------------------------- /templates/partner_list.jinja: -------------------------------------------------------------------------------- 1 | {% extends "public_base_single_column.jinja" %} 2 | 3 | 4 | {%- block header_title -%} 5 | {{ _("Our Partners") }} 6 | {%- endblock header_title -%} 7 | 8 | {%- block section_class %}partners{% endblock section_class -%} 9 | 10 | {% block column_content %} 11 | {%- for county in counties %} 12 |

{{ county.name }} County

13 | 22 | 23 | {%- endfor %} 24 | {% endblock column_content %} -------------------------------------------------------------------------------- /templates/public_base_single_column.jinja: -------------------------------------------------------------------------------- 1 | {% extends "public_base.jinja" %} 2 | 3 | {% block body_class -%}public{%- endblock body_class %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 | {% block column_content %} 11 | {% endblock column_content %} 12 |
13 |
14 |
15 |
16 | {% endblock content %} 17 | -------------------------------------------------------------------------------- /templates/slack/app_bundle_sent.jinja: -------------------------------------------------------------------------------- 1 | {{ org_name }} has: 2 | {%- if unread_count > 0 %} 3 | {{ unread_count }} unread applications. (Emailed {{ oxford_comma(emails) }}) 4 | {%- else %} 5 | no unread applications. (Did not email {{ oxford_comma(emails) }}) 6 | {%- endif %} 7 | {{ update_count }} applications needing status updates out of {{ all_count }} total applications -------------------------------------------------------------------------------- /templates/slack/bundle_action.jinja: -------------------------------------------------------------------------------- 1 | {{ user.email }} {{ action }}{% if submissions|length > 1 %} apps from <{{ bundle_url}}|{{ 2 | oxford_comma(submissions) }}>{% else %} <{{bundle_url}}|{{ submissions[0] }}'s application>{% endif %} -------------------------------------------------------------------------------- /templates/slack/bundle_viewed.jinja: -------------------------------------------------------------------------------- 1 | {{ user.email }} {{ action }}{% if len(submissions) > 1 %} apps from {{ 2 | oxford_comma(submissions) }}{% else %} {{ submissions[0] }}'s app{% endif %} -------------------------------------------------------------------------------- /templates/slack/new_submission.jinja: -------------------------------------------------------------------------------- 1 | New submission #{{submission_count}} to {{ oxford_comma(submission.get_nice_counties())}}! 2 | <{{ request.build_absolute_uri( 3 | url('intake-app_detail', 4 | submission_id=submission.id) 5 | ) }}|{{ submission.get_anonymous_display() }}> 6 | {% if submission.get_nice_contact_preferences() -%} 7 | They want to be contacted via {{ oxford_comma(submission.get_nice_contact_preferences())}} 8 | {% else -%} 9 | They didn't give a contact method 10 | {%- endif %} -------------------------------------------------------------------------------- /templates/slack/notification_failed.jinja: -------------------------------------------------------------------------------- 1 | {{ notification_type }} by {{ oxford_comma(errors.keys()) }} for {{ submission.get_anonymous_display() }} failed with errors: 2 | ```{% for method, error in errors.items() %} 3 | {{ method }}: 4 | {{ error }} 5 | {% endfor %}``` -------------------------------------------------------------------------------- /templates/slack/notification_sent.jinja: -------------------------------------------------------------------------------- 1 | {% for method in methods %} 2 | {%- if method in ['email', 'sms'] -%} 3 | Successfully sent a {{ notification_type }} to {{ submission.anonymous_name }} via {{ method }} 4 | {%- else -%} 5 | Did not send a {{ notification_type }} to {{ submission.anonymous_name }} via {{ method }} 6 | {%- endif %} 7 | {% endfor %} -------------------------------------------------------------------------------- /templates/slack/submission_action.jinja: -------------------------------------------------------------------------------- 1 | {{ user.email }} {{ action }} <{{ submission.get_external_url() }}|{{ submission }}'s application> -------------------------------------------------------------------------------- /templates/slack/submission_deleted.jinja: -------------------------------------------------------------------------------- 1 | {{ user.email }} deleted {{ submission.get_anonymous_display() }}'s application -------------------------------------------------------------------------------- /templates/slack/submission_viewed.jinja: -------------------------------------------------------------------------------- 1 | {{ user.email }} looked at {{ submission.get_anonymous_display() }}'s application -------------------------------------------------------------------------------- /templates/text/applicant_edit_notification.jinja: -------------------------------------------------------------------------------- 1 | Clear My Record update by {{org_name}} 2 | 3 | Your application has been updated with new {{ oxford_comma(changed_fields) }}. 4 | {%- if is_old_contact_info %} 5 | This is the last text message you will get from us at this phone number. 6 | {%- endif %} 7 | 8 | If you did not request this change or would like more info, please {{ org_contact_info }} 9 | to contact {{ org_name }}. -------------------------------------------------------------------------------- /templates/text/confirmation.jinja: -------------------------------------------------------------------------------- 1 | Hi {{ name|safe }}, thanks for applying to Clear My Record.{% for step in next_steps %} {{ step|safe }}{% endfor -%}{%- if unlisted_counties %} For {{ unlisted_counties|safe }} we'll contact you in the next week.{% endif %} 2 | - {{ staff_name }} at Clear My Record -------------------------------------------------------------------------------- /templates/text/followup.jinja: -------------------------------------------------------------------------------- 1 | Hi again {{ name|safe }}, this is {{ staff_name|safe }} from Clear My Record. You applied about a month ago for help in {{ oxford_comma(county_names)|safe }}. 2 | {%- for followup_message in followup_messages %} 3 | {{ followup_message|safe }} 4 | {%- endfor %} -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/tests/__init__.py -------------------------------------------------------------------------------- /tests/sample_pdfs/sample_form.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/tests/sample_pdfs/sample_form.pdf -------------------------------------------------------------------------------- /tests/sample_pdfs/sample_form_filled.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/tests/sample_pdfs/sample_form_filled.pdf -------------------------------------------------------------------------------- /tests/sample_translator.py: -------------------------------------------------------------------------------- 1 | from intake.translators.base import FormToPDFTranslator 2 | 3 | 4 | translate = FormToPDFTranslator({ 5 | 'Given Name Text Box': lambda s: s.answers['first_name'].capitalize(), 6 | 'Family Name Text Box': 'last_name', 7 | 'Address 1 Text Box': 'address_street', 8 | 'Postcode Text Box': 'address_zip', 9 | 'City Text Box': 'address_city' 10 | }, att_object_extractor='answers') 11 | -------------------------------------------------------------------------------- /user_accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/user_accounts/__init__.py -------------------------------------------------------------------------------- /user_accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models 3 | 4 | 5 | class AddressInline(admin.StackedInline): 6 | model = models.Address 7 | extra = 1 8 | 9 | 10 | @admin.register(models.Organization) 11 | class OrganizationAdmin(admin.ModelAdmin): 12 | inlines = [ 13 | AddressInline 14 | ] 15 | 16 | 17 | @admin.register(models.UserProfile) 18 | class UserProfileAdmin(admin.ModelAdmin): 19 | def get_actions(self, request): 20 | actions = super(UserProfileAdmin, self).get_actions(request) 21 | if 'delete_selected' in actions: 22 | del actions['delete_selected'] 23 | return actions 24 | 25 | def has_delete_permission(self, request, obj=None): 26 | return False 27 | -------------------------------------------------------------------------------- /user_accounts/base_views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.mixins import UserPassesTestMixin 2 | 3 | 4 | class StaffOnlyMixin(UserPassesTestMixin): 5 | 6 | def test_func(self): 7 | return self.request.user.is_staff 8 | -------------------------------------------------------------------------------- /user_accounts/exceptions.py: -------------------------------------------------------------------------------- 1 | class MissingInvitationError(Exception): 2 | '''Can't find a matching invitation for a user during signup 3 | ''' 4 | pass 5 | 6 | 7 | class UnacceptedInviteError(Exception): 8 | '''The user has not yet accepted this invite 9 | ''' 10 | pass 11 | 12 | 13 | class UndefinedResourceAccessError(Exception): 14 | '''Someone should not have access to this resource 15 | ''' 16 | pass 17 | 18 | 19 | class NoEmailsForOrgError(Exception): 20 | '''There is no email contact available for this organization 21 | ''' 22 | pass 23 | -------------------------------------------------------------------------------- /user_accounts/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/user_accounts/management/__init__.py -------------------------------------------------------------------------------- /user_accounts/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/user_accounts/management/commands/__init__.py -------------------------------------------------------------------------------- /user_accounts/management/commands/create_email_forwarding_for_users.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from user_accounts.models import UserProfile 4 | from intake.services.mailgun_api_service import set_route_for_user_profile 5 | 6 | 7 | class Command(BaseCommand): 8 | help = 'Creates Mailgun routes for each user profile' 9 | 10 | def handle(self, *args, **options): 11 | self.stdout.write('Sending route creation commands to Mailgun') 12 | for profile in UserProfile.objects.order_by('organization__slug'): 13 | set_route_for_user_profile(profile) 14 | message = '\t{} --> {}'.format( 15 | profile.get_clearmyrecord_email(), 16 | profile.user.email 17 | ) 18 | self.stdout.write(message) 19 | -------------------------------------------------------------------------------- /user_accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9 on 2016-05-24 00:40 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Organization', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('name', models.CharField(max_length=50)), 21 | ('website', models.URLField()), 22 | ('blurb', models.TextField()), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /user_accounts/migrations/0004_userprofile_should_get_notifications.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-06-23 18:28 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 | ('user_accounts', '0003_auto_20160525_1728'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='userprofile', 17 | name='should_get_notifications', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_accounts/migrations/0005_organization_is_receiving_agency.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-06-23 18:30 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 | ('user_accounts', '0004_userprofile_should_get_notifications'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='organization', 17 | name='is_receiving_agency', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_accounts/migrations/0006_add_county_model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-07-16 22:26 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 | ('intake', '0013_add_county_model'), 13 | ('user_accounts', '0005_organization_is_receiving_agency'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='organization', 19 | name='county', 20 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='organizations', to='intake.County'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /user_accounts/migrations/0008_add_profiles_related_name_to_organization.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-08-12 22:46 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 | ('user_accounts', '0007_add_default_orgs_for_counties'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='userprofile', 18 | name='organization', 19 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='profiles', to='user_accounts.Organization'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /user_accounts/migrations/0009_organization_slug.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-08-18 17:27 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 | ('user_accounts', '0008_add_profiles_related_name_to_organization'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='organization', 17 | name='slug', 18 | field=models.SlugField(null=True, unique=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_accounts/migrations/0012_organization_show_pdf_only.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-09-07 04: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 | ('user_accounts', '0011_organization_confirmation_message_contact_info_etc'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='organization', 17 | name='show_pdf_only', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_accounts/migrations/0014_long_and_short_confirmation_messages.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-09-20 01:32 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 | ('user_accounts', '0013_address'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='organization', 17 | old_name='new_submission_confirmation_message', 18 | new_name='long_confirmation_message', 19 | ), 20 | migrations.AddField( 21 | model_name='organization', 22 | name='short_confirmation_message', 23 | field=models.TextField(blank=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /user_accounts/migrations/0015_organization_notify_on_weekends.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-10-07 19:53 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 | ('user_accounts', '0014_long_and_short_confirmation_messages'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='organization', 17 | name='notify_on_weekends', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_accounts/migrations/0017_organization_followup_message_fields.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-12-13 00:29 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 | ('user_accounts', '0016_add_more_invitation_options'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='organization', 17 | name='long_followup_message', 18 | field=models.TextField(blank=True), 19 | ), 20 | migrations.AddField( 21 | model_name='organization', 22 | name='short_followup_message', 23 | field=models.TextField(blank=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /user_accounts/migrations/0019_order_orgs_by_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2017-01-24 21:42 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user_accounts', '0018_org_accepting_apps_checking_notifications'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='organization', 17 | options={'ordering': ['name']}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /user_accounts/migrations/0020_organization_is_live.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2017-01-25 18:05 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 | ('user_accounts', '0019_order_orgs_by_name'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='organization', 17 | name='is_live', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_accounts/migrations/0022_organization_needs_applicant_followups.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-11 21: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 | ('user_accounts', '0021_organization_transfer_partners'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='organization', 17 | name='needs_applicant_followups', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_accounts/migrations/0023_organization_fax_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-05-11 21:32 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 | ('user_accounts', '0022_organization_needs_applicant_followups'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='organization', 17 | name='fax_number', 18 | field=models.TextField(blank=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user_accounts/migrations/0024_userprofile_uuid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-06-16 18:05 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import uuid 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('user_accounts', '0023_organization_fax_number'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='userprofile', 18 | name='uuid', 19 | field=models.UUIDField(default=uuid.uuid4, editable=False), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /user_accounts/migrations/0026_remove_hayward_clean_slate_clinic_from_addresses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.8 on 2017-10-12 21:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | def delete_hayward_clean_slate_clinic(apps, schema_editor): 9 | Address = apps.get_model('user_accounts', 'Address') 10 | Address.objects.filter(name='Hayward Clean Slate Walk-in Clinic').delete() 11 | 12 | 13 | class Migration(migrations.Migration): 14 | 15 | dependencies = [ 16 | ('user_accounts', '0025_purgedorganization'), 17 | ] 18 | 19 | operations = [ 20 | migrations.RunPython(delete_hayward_clean_slate_clinic) 21 | ] 22 | -------------------------------------------------------------------------------- /user_accounts/migrations/0027_remove_oakland_clinic_from_addresses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations 4 | 5 | 6 | def delete_oakland_clean_slate_clinic(apps, schema_editor): 7 | Address = apps.get_model('user_accounts', 'Address') 8 | Address.objects.filter(name='Oakland Clean Slate Walk-in Clinic', 9 | organization__slug='a_pubdef').delete() 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ('user_accounts', '0026_remove_hayward_clean_slate_clinic_from_addresses'), 16 | ] 17 | 18 | operations = [ 19 | migrations.RunPython(delete_oakland_clean_slate_clinic) 20 | ] 21 | -------------------------------------------------------------------------------- /user_accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/user_accounts/migrations/__init__.py -------------------------------------------------------------------------------- /user_accounts/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .address import Address 2 | from .invitation import Invitation 3 | from .organization import Organization, OrganizationManager 4 | from .user_profile import UserProfile, get_user_display 5 | 6 | __all__ = [ 7 | Address, 8 | Invitation, 9 | Organization, OrganizationManager, 10 | UserProfile, get_user_display, 11 | ] 12 | -------------------------------------------------------------------------------- /user_accounts/models/address.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Address(models.Model): 5 | organization = models.ForeignKey( 6 | 'user_accounts.Organization', 7 | on_delete=models.CASCADE, 8 | related_name='addresses') 9 | name = models.TextField() 10 | text = models.TextField() 11 | walk_in_hours = models.TextField(blank=True) 12 | 13 | class Meta: 14 | verbose_name_plural = "Addresses" 15 | 16 | def __str__(self): 17 | return self.text 18 | -------------------------------------------------------------------------------- /user_accounts/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | from .user_serializer import UserMixpanelSerializer 2 | from .shortcuts import mixpanel_user_data 3 | 4 | __all__ = [ 5 | UserMixpanelSerializer, 6 | mixpanel_user_data 7 | ] 8 | -------------------------------------------------------------------------------- /user_accounts/serializers/shortcuts.py: -------------------------------------------------------------------------------- 1 | from .user_serializer import UserMixpanelSerializer 2 | 3 | 4 | def mixpanel_user_data(user): 5 | return UserMixpanelSerializer(user).data 6 | -------------------------------------------------------------------------------- /user_accounts/serializers/user_serializer.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from intake.serializers.fields import ChainableAttributeField 3 | 4 | 5 | class UserMixpanelSerializer(serializers.Serializer): 6 | """This serializes user information from a user instance 7 | """ 8 | user_organization_name = ChainableAttributeField( 9 | 'profile.organization.name') 10 | user_username = ChainableAttributeField('username') 11 | -------------------------------------------------------------------------------- /user_accounts/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import shared_task 2 | 3 | 4 | @shared_task 5 | def create_mailgun_route(user_profile_id): 6 | from intake.services.mailgun_api_service import set_route_for_user_profile 7 | from user_accounts.models import UserProfile 8 | # TODO: Fix whatever is causing circular imports. 9 | profile = UserProfile.objects.get(pk=user_profile_id) 10 | set_route_for_user_profile(profile) 11 | -------------------------------------------------------------------------------- /user_accounts/templates/account/email/password_reset_key_message.txt: -------------------------------------------------------------------------------- 1 | Hello, 2 | 3 | You're receiving this e-mail because you or someone else has requested a password for your account at Clear My Record. 4 | 5 | If you would like to reset your password, follow the link below: 6 | 7 | {{ password_reset_url }} 8 | 9 | If you did not request a password reset, you can safely ignore this email. -------------------------------------------------------------------------------- /user_accounts/templates/account/email/password_reset_key_subject.txt: -------------------------------------------------------------------------------- 1 | Password Reset for Clear My Record -------------------------------------------------------------------------------- /user_accounts/templates/invitations/email/email_invite_message.txt: -------------------------------------------------------------------------------- 1 | Hello, 2 | 3 | You've been invited to create an account on Clear My Record. 4 | 5 | If you'd like to join, please follow this link: 6 | {{ invite_url }} -------------------------------------------------------------------------------- /user_accounts/templates/invitations/email/email_invite_subject.txt: -------------------------------------------------------------------------------- 1 | Invitation to create an account on Clear My Record 2 | -------------------------------------------------------------------------------- /user_accounts/templates/user_accounts/invite_form.jinja: -------------------------------------------------------------------------------- 1 | {% extends "admin_form_base.jinja" %} 2 | {% import 'macros.jinja' as macros %} 3 | 4 | {%- block header_title -%} 5 | Invite a new user 6 | {%- endblock header_title -%} 7 | {% block form %} 8 |
9 |

Please add an email and organization below. The user will recieve an email with instructions.

10 | {% include "includes/csrf_field.jinja" %} 11 | {{ macros.render_form_fields(form) }} 12 |

13 | 14 |

15 |
16 | {% endblock form %} -------------------------------------------------------------------------------- /user_accounts/templates/user_accounts/password_reset_sent.jinja: -------------------------------------------------------------------------------- 1 | {% extends "admin_base_single_column.jinja" %} 2 | {% block head_title %}Password Reset Sent{% endblock %} 3 | {%- block header_title -%} 4 | We've sent you an email. 5 | {%- endblock header_title -%} 6 | {% block column_content %} 7 |

We've sent you an email with a link to reset your password. If you don't see it within a few minutes, let us know.

8 | {% endblock column_content %} -------------------------------------------------------------------------------- /user_accounts/templates/user_accounts/request_password_reset.jinja: -------------------------------------------------------------------------------- 1 | {% extends "admin_form_base.jinja" %} 2 | {% import 'macros.jinja' as macros %} 3 | {% block head_title %}Reset password{% endblock %} 4 | {%- block header_title -%} 5 | Request a password reset 6 | {%- endblock header_title -%} 7 | {% block form %} 8 |

Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it.

9 | 10 |
11 | {% include "includes/csrf_field.jinja" %} 12 | {{ macros.render_form_fields(form) }} 13 |

14 | 15 |

16 |
17 | {% endblock form %} -------------------------------------------------------------------------------- /user_accounts/templates/user_accounts/signup.jinja: -------------------------------------------------------------------------------- 1 | {% extends "admin_form_base.jinja" %} 2 | {% import 'macros.jinja' as macros %} 3 | {% block head_title %}Sign up{% endblock %} 4 | {%- block header_title -%} 5 | Sign up 6 | {%- endblock header_title -%} 7 | {% block form %} 8 | 19 | {% endblock form %} -------------------------------------------------------------------------------- /user_accounts/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/user_accounts/tests/__init__.py -------------------------------------------------------------------------------- /user_accounts/tests/clients.py: -------------------------------------------------------------------------------- 1 | from django.test import Client 2 | 3 | 4 | class CsrfClient(Client): 5 | 6 | def __init__(self, **defaults): 7 | super().__init__(enforce_csrf_checks=True, **defaults) 8 | 9 | def get_csrf_token(self, response): 10 | return response.cookies['csrftoken'].value 11 | 12 | def fill_form(self, url, csrf_token=None, headers=None, **data): 13 | if not csrf_token: 14 | response = self.get(url) 15 | csrf_token = self.get_csrf_token(response) 16 | follow = data.pop('follow', False) 17 | data.update(csrfmiddlewaretoken=csrf_token) 18 | headers = headers or {} 19 | return self.post(url, data, follow=follow, **headers) 20 | -------------------------------------------------------------------------------- /user_accounts/tests/factories/__init__.py: -------------------------------------------------------------------------------- 1 | from .user_factory import UserFactory 2 | from .user_profile_factory import ( 3 | UserProfileFactory, 4 | fake_app_reviewer, 5 | profile_for_org_and_group_names, 6 | profile_for_slug_in_groups, 7 | app_reviewer, 8 | followup_user, 9 | monitor_user 10 | ) 11 | from .organization_factory import ( 12 | ExistingOrganizationFactory, FakeOrganizationFactory) 13 | 14 | 15 | __all__ = [ 16 | UserFactory, 17 | UserProfileFactory, 18 | fake_app_reviewer, 19 | profile_for_org_and_group_names, 20 | profile_for_slug_in_groups, 21 | app_reviewer, 22 | followup_user, 23 | monitor_user, 24 | ExistingOrganizationFactory, 25 | FakeOrganizationFactory, 26 | ] 27 | -------------------------------------------------------------------------------- /user_accounts/tests/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/intake/8755e64c13e2b6f9bef9bbee47011253f20e7e0d/user_accounts/tests/models/__init__.py --------------------------------------------------------------------------------