├── .env.example ├── .gitignore ├── .travis.yml ├── LICENSE.mkd ├── Procfile ├── README.md ├── alembic.ini ├── alembic ├── README ├── env.py ├── script.py.mako └── versions │ ├── 10855dae57b7_add_foreign_keys_bet.py │ ├── 134e5fea7601_add_active_flag_to_user_table.py │ └── 2de765e466ca_convert_created_date.py ├── app.json ├── backend_tests.py ├── close_requests_spam.py ├── db_seed.py ├── db_setup.py ├── db_users.py ├── docs ├── about.md ├── admin.md ├── images │ ├── add_helper.png │ ├── add_link.png │ ├── add_note.png │ ├── admin_delete_note.png │ ├── admin_delete_qa.png │ ├── admin_delete_record.png │ ├── admin_delete_request.png │ ├── admin_delete_user.png │ ├── admin_edit_note.png │ ├── admin_edit_prr_liaison.png │ ├── admin_edit_qa.png │ ├── admin_edit_request.png │ ├── admin_note.png │ ├── admin_prr_liaison.png │ ├── admin_qa.png │ ├── admin_record.png │ ├── admin_remove_record.png │ ├── admin_request.png │ ├── all_request_table.png │ ├── answer_question.png │ ├── ask_question.png │ ├── change_point_of_contact.png │ ├── city_url.png │ ├── close_request.png │ ├── extend_request.png │ ├── filter.png │ ├── generic_request.png │ ├── liaisons-csv.png │ ├── new_request.png │ ├── not_city_record.png │ ├── offline_record.png │ ├── offline_record_example.png │ ├── public_filter.png │ ├── public_note.png │ ├── public_table.png │ ├── receipt_information.png │ ├── reopen_request.png │ ├── responses.png │ ├── share_request.png │ ├── staff_csv.png │ ├── staff_filter.png │ ├── staff_new_request.png │ ├── staff_table.png │ ├── status.png │ ├── subscribe.png │ ├── track_request.png │ ├── unsubscribe.png │ └── upload_record.png ├── local_installation.md ├── redeploy.md ├── requester.md └── responder.md ├── index.html ├── newrelic.ini ├── public_records_portal ├── RequestPresenter.py ├── ResponsePresenter.py ├── __init__.py ├── akismet.py ├── csv_export.py ├── db_helpers.py ├── filters.py ├── gravatar.py ├── helpers.py ├── models.py ├── notifications.py ├── prflask.py ├── prr.py ├── scribd_helpers.py ├── spam.py ├── static │ ├── css │ │ ├── _response_widget.css │ │ ├── about.css │ │ ├── admin.css │ │ ├── all_requests.css │ │ ├── all_requests_noresults.css │ │ ├── base.css │ │ ├── bottom_sidebar.css │ │ ├── case.css │ │ ├── feedback.css │ │ ├── landing.css │ │ ├── manage_request_city.css │ │ ├── manage_request_city_less_js.css │ │ ├── missing_email.css │ │ ├── new_request.css │ │ ├── plugins │ │ │ ├── BootSwatchr6.css │ │ │ ├── DT_bootstrap.css │ │ │ ├── bootstrap-combobox.css │ │ │ ├── bootstrap-fileupload.css │ │ │ ├── bootstrap-responsive.css │ │ │ ├── bootstrap-responsive.min.css │ │ │ ├── bootstrap-select.min.css │ │ │ ├── bootstrap.min.css │ │ │ ├── bootstro.css │ │ │ ├── font-awesome.min.css │ │ │ ├── font │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ └── fontawesome-webfont.woff │ │ │ ├── images │ │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ │ ├── ui-icons_222222_256x240.png │ │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ │ ├── ui-icons_454545_256x240.png │ │ │ │ ├── ui-icons_888888_256x240.png │ │ │ │ └── ui-icons_cd0a0a_256x240.png │ │ │ ├── jquery-ui-1.10.4.custom.min.css │ │ │ ├── jquery-ui.css │ │ │ ├── jquery.dataTables.css │ │ │ └── select2.css │ │ ├── recaptcha.css │ │ ├── sidebar.css │ │ ├── signup.css │ │ ├── social_media.css │ │ └── staff_card.css │ ├── examples │ │ ├── liaisons.csv │ │ ├── logo.png │ │ ├── logo_black.png │ │ └── staff.csv │ ├── images │ │ ├── CfA_logo.png │ │ ├── CityTree_logo_green.png │ │ ├── create.png │ │ ├── explore.png │ │ ├── favicon.ico │ │ ├── flagtag_white.png │ │ ├── logo.png │ │ ├── logo_black.png │ │ ├── recordtrac_logo.png │ │ ├── recordtrac_logo_nomargin_whttxt.png │ │ ├── track.png │ │ └── valid.png │ ├── js │ │ ├── _response_widget.js │ │ ├── all_requests.js │ │ ├── backbone-min.js │ │ ├── case.js │ │ ├── custom.bootstrap-select.js │ │ ├── custom.js │ │ ├── date_picker.js │ │ ├── include.js │ │ ├── manage_request_city.js │ │ ├── manage_request_city_less_js.js │ │ ├── new_request.js │ │ ├── placeholder.js │ │ ├── plugins │ │ │ ├── DT_bootstrap.js │ │ │ ├── accessifyhtml5.min.js │ │ │ ├── bootstrap-combobox.js │ │ │ ├── bootstrap-fileupload.js │ │ │ ├── bootstrap-select.min.js │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.min.js │ │ │ ├── bootstro.js │ │ │ ├── css3-mediaqueries.js │ │ │ ├── custom.DT_bootstrap.js │ │ │ ├── custom.bootstro.js │ │ │ ├── d3.v3.min.js │ │ │ ├── include.js │ │ │ ├── jquery-1.11.1.min.js │ │ │ ├── jquery-ui-1.10.4.custom.min.js │ │ │ ├── jquery.dataTables.js │ │ │ ├── jquery.ellipsis.js │ │ │ ├── jquery.placeholder.js │ │ │ ├── jquery.validate.js │ │ │ ├── modernizr-latest-min.js │ │ │ ├── rem.min.js │ │ │ ├── require.js │ │ │ └── selectivizr-min.js │ │ ├── requests.js │ │ ├── responses_viz.js │ │ ├── track.js │ │ └── underscore-min.js │ └── json │ │ ├── README.md │ │ ├── actions.json │ │ ├── emails.json │ │ ├── notcityrecords_example.json │ │ └── tutorial.json ├── templates │ ├── 404.html │ ├── _add_person.html │ ├── _ask_question.html │ ├── _contact_popover.html │ ├── _history.html │ ├── _manage_requests_status.html │ ├── _public_note.html │ ├── _reroute.html │ ├── _response_widget.html │ ├── _staff_participants.html │ ├── _timestamp.html │ ├── about.html │ ├── actions.html │ ├── admin.html │ ├── all_requests.html │ ├── all_requests_less_js.html │ ├── alpha.html │ ├── base.html │ ├── case.html │ ├── closed.html │ ├── error.html │ ├── generic.html │ ├── generic_email.html │ ├── help_text.html │ ├── help_with_uploads.html │ ├── landing.html │ ├── manage_request_city.html │ ├── manage_request_city_less_js.html │ ├── manage_request_feedback.html │ ├── manage_request_follower.html │ ├── manage_request_new.html │ ├── manage_request_public.html │ ├── manage_request_unfollow.html │ ├── missing_email.html │ ├── new_request.html │ ├── new_request_email.html │ ├── offline_request.html │ ├── password_email.html │ ├── recaptcha_answer.html │ ├── recaptcha_note.html │ ├── recaptcha_request.html │ ├── reset_password.html │ ├── response.html │ ├── signup.html │ ├── staff_card.html │ ├── track.html │ ├── tutorial.html │ └── uploaded.html ├── timeout.py └── views.py ├── requirements.txt ├── run.py ├── scripts └── run-server.sh ├── send_notifications.py ├── testing.env └── tests.py /.env.example: -------------------------------------------------------------------------------- 1 | ENVIRONMENT=LOCAL 2 | APPLICATION_URL=http://127.0.0.1:5000/ 3 | DATABASE_URL=postgresql://localhost/recordtrac 4 | AGENCY_NAME='Your agency name' 5 | SECRET_KEY='change this before deploying to production' 6 | DEFAULT_OWNER_EMAIL=citystaff@city.gov 7 | DEFAULT_OWNER_REASON='Open records coordinator' 8 | DAYS_TO_FULFILL=10 9 | DAYS_AFTER_EXTENSION=14 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | settings.cfg 3 | websiteconfig.py 4 | settings.env 5 | notes/ 6 | .DS_Store 7 | # @waltz's emacs gitignore partee pak 8 | *~ 9 | .#* 10 | #~# 11 | .env -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | python: 2 | - "2.7" 3 | 4 | env: 5 | - TESTING=true DATABASE_URL=postgresql://localhost/recordtrac 6 | 7 | install: 8 | - sudo pip install -r requirements.txt 9 | 10 | before_script: 11 | - psql -c 'create database recordtrac;' -U postgres 12 | 13 | script: 14 | - python tests.py -------------------------------------------------------------------------------- /LICENSE.mkd: -------------------------------------------------------------------------------- 1 | Copyright 2013-2014 Code for America 2 | 3 | # Affero GNU Public License 3.0 4 | 5 | The full license text is at http://www.gnu.org/licenses/agpl-3.0.html 6 | 7 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn -w 3 -t 360 public_records_portal.prflask:app 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RecordTrac 2 | This is the home page for documentation for the **RecordTrac** application. 3 | 4 | * [What is RecordTrac](#what-is-recordtrac) 5 | * [How to create a new RecordTrac app](#how-to-create-a-new-recordtrac-app-for-your-agency) 6 | * [Technical details and developer documentation](#technical-details) 7 | * [How to use RecordTrac](#how-to-use-recordtrac-as-a-requester) 8 | 9 | For help contact [Code for America](http://www.codeforamerica.org/apps/recordtrac) 10 | 11 | ## What is RecordTrac? 12 | RecordTrac is a public record request management tool for government agencies. 13 | ![RecordTrac](/docs/images/generic_request.png "RecordTrac") 14 | This web application allows government employees manage, respond to, and fulfill incoming requests. It also provides a quick, simple way for individuals to submit public record requests. RecordTrac displays all requests and responsive documents so that members of the public may find what they need without submitting additional public record requests. 15 | 16 | [More about RecordTrac...](/docs/about.md) 17 | 18 | ## [How to create a new RecordTrac app for your agency](/docs/redeploy.md) 19 | * Groundwork 20 | * Best Practices 21 | * Redeploy on Heroku 22 | 23 | ### To create a development environment, you can [install RecordTrac locally](/docs/local_installation.md). 24 | 25 | ## Technical details 26 | RecordTrac is primarily a Python application built on a backend Flask framework and a frontend Bootstrap framework. 27 | 28 | RecordTrac is compatible with most modern browsers, including Internet Explorer 8 and above. 29 | 30 | ### Platform pieces 31 | * [Flask 0.10](http://flask.pocoo.org/) is used as the backend Python framework. 32 | * [Bootstrap 2.3.2](http://getbootstrap.com/2.3.2) is used as the frontend HTML/CSS/JS framework. 33 | * [Postgres 9.3.0](http://www.postgresapp.com/) is used to manage the database. 34 | * [Python 2.7.5](http://www.python.org/getit) is used as the foundational programming language. 35 | 36 | ### Plugins 37 | * [Font Awesome 3.2.1](http://fortawesome.github.io/Font-Awesome) is used to render all the icons on the site. 38 | * A number of Javascript libraries like [Backbone.js 1.1.2](http://backbonejs.org/#) are used, primarily for the search function. 39 | 40 | ### Service dependencies 41 | * [SendGrid](http://sendgrid.com/) is used for all email notifications about requests. 42 | * [Akismet](http://akismet.com/) is used as a spam filter. 43 | * [Captcha](http://www.captcha.net/) is used to allow requesters to override the spam filter if necessary. 44 | * [Mozilla Persona](https://login.persona.org/) is used to manage user authentication and password management. 45 | 46 | ### Feature backlog 47 | Issues and feature backlog are tracked through [GitHub issues](https://github.com/codeforamerica/recordtrac/issues). 48 | 49 | ### Developer documentation 50 | * [Browse full source code on GitHub](https://github.com/codeforamerica/recordtrac) 51 | * [Technical docs](http://codeforamerica.github.io/recordtrac/docs/2.0.0/technical-docs.html) 52 | 53 | ## How to use RecordTrac... as a requester 54 | * [Search for a record](/docs/requester.md) 55 | * [Make a new request](/docs/requester.md#make-a-request) 56 | 57 | ## How to use RecordTrac... as an agency employee 58 | * [Search for a record](/docs/responder.md) 59 | * [Manage your request list](/docs/responder.md#manage-your-request-list) 60 | * [Make a new request](/docs/responder.md#make-a-new-request) 61 | * [Respond to a request](/docs/responder.md#respond-to-a-request) 62 | * [Admin](/docs/admin.md) 63 | 64 | -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = alembic 6 | 7 | # template used to generate migration files 8 | # file_template = %%(rev)s_%%(slug)s 9 | 10 | # set to 'true' to run the environment during 11 | # the 'revision' command, regardless of autogenerate 12 | # revision_environment = false 13 | 14 | sqlalchemy.url = driver://user:pass@localhost/dbname 15 | 16 | 17 | # Logging configuration 18 | [loggers] 19 | keys = root,sqlalchemy,alembic 20 | 21 | [handlers] 22 | keys = console 23 | 24 | [formatters] 25 | keys = generic 26 | 27 | [logger_root] 28 | level = WARN 29 | handlers = console 30 | qualname = 31 | 32 | [logger_sqlalchemy] 33 | level = WARN 34 | handlers = 35 | qualname = sqlalchemy.engine 36 | 37 | [logger_alembic] 38 | level = INFO 39 | handlers = 40 | qualname = alembic 41 | 42 | [handler_console] 43 | class = StreamHandler 44 | args = (sys.stderr,) 45 | level = NOTSET 46 | formatter = generic 47 | 48 | [formatter_generic] 49 | format = %(levelname)-5.5s [%(name)s] %(message)s 50 | datefmt = %H:%M:%S 51 | -------------------------------------------------------------------------------- /alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /alembic/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import sys 3 | import os.path 4 | sys.path.append( 5 | os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 6 | 7 | import public_records_portal 8 | from public_records_portal import db, app 9 | 10 | from alembic import context 11 | from sqlalchemy import engine_from_config, pool 12 | from logging.config import fileConfig 13 | 14 | # this is the Alembic Config object, which provides 15 | # access to the values within the .ini file in use. 16 | config = context.config 17 | config.set_main_option("sqlalchemy.url", app.config['SQLALCHEMY_DATABASE_URI']) 18 | 19 | # Interpret the config file for Python logging. 20 | # This line sets up loggers basically. 21 | fileConfig(config.config_file_name) 22 | 23 | # add your model's MetaData object here 24 | # for 'autogenerate' support 25 | # from myapp import mymodel 26 | target_metadata = db.metadata 27 | # target_metadata = None 28 | 29 | # other values from the config, defined by the needs of env.py, 30 | # can be acquired: 31 | # my_important_option = config.get_main_option("my_important_option") 32 | # ... etc. 33 | 34 | def run_migrations_offline(): 35 | """Run migrations in 'offline' mode. 36 | 37 | This configures the context with just a URL 38 | and not an Engine, though an Engine is acceptable 39 | here as well. By skipping the Engine creation 40 | we don't even need a DBAPI to be available. 41 | 42 | Calls to context.execute() here emit the given string to the 43 | script output. 44 | 45 | """ 46 | url = config.get_main_option("sqlalchemy.url") 47 | context.configure(url=url) 48 | 49 | with context.begin_transaction(): 50 | context.run_migrations() 51 | 52 | def run_migrations_online(): 53 | """Run migrations in 'online' mode. 54 | 55 | In this scenario we need to create an Engine 56 | and associate a connection with the context. 57 | 58 | """ 59 | engine = engine_from_config( 60 | config.get_section(config.config_ini_section), 61 | prefix='sqlalchemy.', 62 | poolclass=pool.NullPool) 63 | 64 | connection = engine.connect() 65 | context.configure( 66 | connection=connection, 67 | target_metadata=target_metadata 68 | ) 69 | 70 | try: 71 | with context.begin_transaction(): 72 | context.run_migrations() 73 | finally: 74 | connection.close() 75 | 76 | if context.is_offline_mode(): 77 | run_migrations_offline() 78 | else: 79 | run_migrations_online() 80 | 81 | -------------------------------------------------------------------------------- /alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = ${repr(up_revision)} 11 | down_revision = ${repr(down_revision)} 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | ${imports if imports else ""} 16 | 17 | def upgrade(): 18 | ${upgrades if upgrades else "pass"} 19 | 20 | 21 | def downgrade(): 22 | ${downgrades if downgrades else "pass"} 23 | -------------------------------------------------------------------------------- /alembic/versions/10855dae57b7_add_foreign_keys_bet.py: -------------------------------------------------------------------------------- 1 | """add foreign keys between User and Department 2 | 3 | Revision ID: 10855dae57b7 4 | Revises: 134e5fea7601 5 | Create Date: 2015-05-19 10:59:16.315012 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '10855dae57b7' 11 | down_revision = '134e5fea7601' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | op.alter_column('user', 'department', new_column_name='department_id') 19 | op.add_column('department', sa.Column('primary_contact_id', sa.INTEGER, sa.ForeignKey("user.id"))) 20 | op.add_column('department', sa.Column('backup_contact_id', sa.INTEGER, sa.ForeignKey("user.id"))) 21 | 22 | 23 | def downgrade(): 24 | op.drop_column('department', 'primary_contact_id') 25 | op.drop_column('department', 'backup_contact_id') 26 | op.alter_column('user', 'department_id', new_column_name='department') 27 | -------------------------------------------------------------------------------- /alembic/versions/134e5fea7601_add_active_flag_to_user_table.py: -------------------------------------------------------------------------------- 1 | """Add active flag to User table 2 | 3 | Revision ID: 134e5fea7601 4 | Revises: None 5 | Create Date: 2014-07-09 16:55:35.820805 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '134e5fea7601' 11 | down_revision = None 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | def upgrade(): 17 | ### commands auto generated by Alembic - please adjust! ### 18 | op.add_column('user', sa.Column('is_staff', sa.Boolean(), server_default='False')) 19 | ### end Alembic commands ### 20 | 21 | def downgrade(): 22 | ### commands auto generated by Alembic - please adjust! ### 23 | op.drop_column('user', 'is_staff') 24 | ### end Alembic commands ### 25 | 26 | -------------------------------------------------------------------------------- /alembic/versions/2de765e466ca_convert_created_date.py: -------------------------------------------------------------------------------- 1 | """convert created datetimes to utc 2 | 3 | Revision ID: 2de765e466ca 4 | Revises: 10855dae57b7 5 | Create Date: 2015-05-19 23:01:13.266389 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '2de765e466ca' 11 | down_revision = '10855dae57b7' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | from datetime import datetime 16 | from pytz import timezone, utc 17 | 18 | pacific = timezone('US/Pacific') 19 | Request = sa.Table( 20 | 'request', 21 | sa.MetaData(), 22 | sa.Column('id', sa.Integer, primary_key=True), 23 | sa.Column('date_created', sa.DateTime), 24 | ) 25 | 26 | def upgrade(): 27 | connection = op.get_bind() 28 | 29 | # Assume all existing requests were entered in Pacific time, and 30 | # convert them to UTC. 31 | for request in connection.execute(Request.select()): 32 | date_created = request.date_created.replace(tzinfo=pacific) 33 | date_created = date_created.astimezone(utc) 34 | connection.execute( 35 | Request.update().where(Request.c.id == request.id) 36 | .values(date_created=date_created) 37 | ) 38 | 39 | 40 | def downgrade(): 41 | connection = op.get_bind() 42 | 43 | # Assume all existing requests were entered in UTC, and convert 44 | # them to Pacific time. 45 | for request in connection.execute(Request.select()): 46 | date_created = request.date_created.replace(tzinfo=utc) 47 | date_created = date_created.astimezone(pacific) 48 | connection.execute( 49 | Request.update().where(Request.c.id == request.id) 50 | .values(date_created=date_created) 51 | ) 52 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RecordTrac", 3 | "description": "Manage and display public record requests", 4 | "website": "https://github.com/codeforamerica/recordtrac", 5 | "success_url": "/", 6 | "addons": ["heroku-postgresql:hobby-dev"], 7 | "env": { 8 | "ENVIRONMENT": "PRODUCTION" 9 | } 10 | } -------------------------------------------------------------------------------- /backend_tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from public_records_portal import notifications, db_helpers 4 | 5 | # Here's our "unit tests". 6 | class BackEndTests(unittest.TestCase): 7 | 8 | def testShouldNotify(self): 9 | self.failIf(notifications.should_notify('jrivers@oaklandnet.com')) 10 | def testShouldNotNotify(self): 11 | self.failUnless(notifications.should_notify('nneditch@oaklandnet.com')) 12 | def testGetObjectWrongParams(self): 13 | self.failIf(db_helpers.get_obj("fake object", "not an ID")) 14 | def testGetObject(self): 15 | self.failUnless(db_helpers.get_obj("Request", 10)) 16 | def testGetStaffRecipients(self): 17 | self.failUnless(notifications.get_staff_recipients(db_helpers.get_obj("Request", 10))) 18 | 19 | 20 | def main(): 21 | unittest.main() 22 | 23 | if __name__ == '__main__': 24 | main() -------------------------------------------------------------------------------- /close_requests_spam.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from public_records_portal import models, prr 3 | 4 | arg_length = len(sys.argv) 5 | if arg_length != 4: 6 | print 'Enter your user_id followed by the first request_id and the last request_id' 7 | else: 8 | user_id = sys.argv[1] 9 | u = models.User.query.get(user_id) 10 | if u: 11 | begin = int(sys.argv[2]) 12 | end = int(sys.argv[3]) 13 | if begin < end: 14 | for request_id in range(begin, end + 1): 15 | print "Closing request %s" %(request_id) 16 | prr.close_request_spam(user_id = user_id, request_id = request_id) 17 | print 'Successfully closed requests %s to %s' %(begin, end) 18 | else: 19 | 'The range you entered is invalid. Please enter a valid range.' 20 | else: 21 | print 'You entered %s for a user_id, which does not exist. Please enter a valid user_id' %(user_id) 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /db_seed.py: -------------------------------------------------------------------------------- 1 | from public_records_portal import prr, db_helpers, db, models 2 | from public_records_portal.prflask import app 3 | import os, random, string, json 4 | from datetime import datetime, timedelta 5 | 6 | print "Seeding database..." 7 | 8 | common_requests = ['City Council meeting minutes', 'Police Report', 'Incident Report', 'Communication between Councilmembers'] 9 | 10 | departments = [d.name for d in models.Department.query.all()] 11 | people = [d.email for d in models.User.query.all()] 12 | 13 | reasons = ['They have the document', 'They would know more about this', 'They are my backup', 'Can you look into this?'] 14 | documents = ['Minutes', 'Report'] 15 | answers = ["Yep, thanks so much!", "No, nevermind then."] 16 | 17 | # Create some seed data so our tests run 18 | for i in range(20): 19 | request_type = random.choice(common_requests) 20 | request_department = random.choice(departments) 21 | random_number = random.randrange(0, 901, 4) 22 | another_random_number = random.randrange(0, 901, 4) 23 | request_text = "%(request_type)s %(random_number)s" % locals() 24 | four_days_ago = (datetime.now() - timedelta(days = 4)) 25 | request_id, success = prr.make_request(text=request_text, department = request_department, date_received = four_days_ago, passed_spam_filter = True) 26 | if success: 27 | prr.add_note(request_id = request_id, text = "We're working on this and will get back to you shortly.", user_id = 1) 28 | qa_id = prr.ask_a_question(request_id = request_id, user_id = 1, question = "You specified %(random_number)s, but that does not exist. Did you mean %(another_random_number)s? " % locals()) 29 | if qa_id: 30 | answer = random.choice(answers) 31 | prr.answer_a_question(qa_id = qa_id, answer = answer) 32 | if "Yep" in answer: 33 | prr.add_link(request_id = request_id, url = "http://www.codeforamerica.org", description = "Report %(another_random_number)s" % locals(), user_id = 1) 34 | else: 35 | prr.close_request(request_id = request_id, reason = "Record does not exist.", user_id = 1) 36 | prr.assign_owner(request_id = request_id, reason = random.choice(reasons), email = random.choice(people)) 37 | db_helpers.add_staff_participant(request_id = request_id, email = random.choice(people), reason = random.choice(reasons)) 38 | 39 | 40 | -------------------------------------------------------------------------------- /db_setup.py: -------------------------------------------------------------------------------- 1 | from public_records_portal import app, models, db 2 | 3 | models.db.create_all() 4 | 5 | -------------------------------------------------------------------------------- /db_users.py: -------------------------------------------------------------------------------- 1 | from public_records_portal.prr import set_directory_fields 2 | 3 | # Set directory fields from CSV data supplied (via STAFF_URL and LIAISONS_URL) 4 | set_directory_fields() 5 | -------------------------------------------------------------------------------- /docs/about.md: -------------------------------------------------------------------------------- 1 | # About RecordTrac 2 | ## What Is RecordTrac? 3 | RecordTrac is a public record request management tool for government agencies. This web application allows government employees manage, respond to, and fulfill incoming requests. It also provides a quick, simple way for individuals to submit public record requests. RecordTrac displays all requests and responsive documents so that members of the public may find what they need without submitting additional public record requests. 4 | 5 | ## Why was RecordTrac built? 6 | 7 | The 2013 Code for America fellows created RecordTrac with the City of Oakland. As public records requests became larger and more complex, city employees found coordinating a response to be difficult. The old software system no longer met their needs and many requests remained untracked. 8 | 9 | Members of the public complained they didn’t understand what happened after they submitted their requests and it took too long to get the records they needed. 10 | 11 | By making everything completely transparent, it became easier for the City and members of the public to understand what happens to each request. Although this app was initially developed for the City of Oakland, any governmental agencies who is required by law to respond to requests for information or documents can reuse RecordTrac. 12 | 13 | ## Design Principles 14 | 15 | The primary goal was to create a central, shared view for all public records requests and responses from government employees. 16 | 17 | * **Minimum in, maximum out**: Only ask what you need of requesters to fulfill a public records request. 18 | 19 | * **Transparency**: Government employees and members of the public share a view of all requests and how they are being fulfilled. 20 | 21 | * **Simple and fun**: It should be easy to submit a public records requests. 22 | 23 | * **Comfortable and confident**: Users should not be intimidated and should fully understand what happens when they take an action. 24 | 25 | * **Engaging**: Requesters should be able to easily interact with government employees and share information with others. 26 | 27 | RecordTrac is not designed to be a document management system or a tool to help government employees redact sensitive or confidential records. It is not a timer, keeping track of how long a government employee has to fulfill a records request. Although you can learn about how a particular governmental agency fulfills a public records request by using RecordTrac, it is not designed to teach about about freedom of information laws. 28 | 29 | 30 | ## Research and references 31 | 32 | The 2013 Code for America fellows conducted research on the way the City of Oakland managed and fulfilled public records requests. This influenced the web app's development. Links to the research can be found below: 33 | 34 | * [How the City of Oakland handled public record requests](http://codeforamerica.github.io/recordtrac/docs/1.0.0/research.html) 35 | 36 | * [Analysis of past public record requests](http://codeforamerica.github.io/recordtrac/docs/1.0.0/ibpm-analysis.html) 37 | 38 | The Code for America fellows piloted RecordTrac in the City of Oakland before it was released to the public on October 1, 2013. To learn about the testing strategy, visit the following web page: 39 | 40 | * [Testing Strategy](http://codeforamerica.github.io/recordtrac/docs/1.0.0/teststrategy.html) for RecordTrac 41 | 42 | 43 | The Oakland application was built on the California Public Records Act and Oakland's Sunshine Ordinance: 44 | 45 | * [California Public Records Act](http://x.genius.com/State-of-california-california-public-records-act-annotated) 46 | 47 | * [City of Oakland Sunshine Ordinance](https://library.municode.com/HTML/16308/level2/TIT2ADPE_CH2.20PUMEPURE.html#TOPTITLE) 48 | -------------------------------------------------------------------------------- /docs/images/add_helper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/add_helper.png -------------------------------------------------------------------------------- /docs/images/add_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/add_link.png -------------------------------------------------------------------------------- /docs/images/add_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/add_note.png -------------------------------------------------------------------------------- /docs/images/admin_delete_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_delete_note.png -------------------------------------------------------------------------------- /docs/images/admin_delete_qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_delete_qa.png -------------------------------------------------------------------------------- /docs/images/admin_delete_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_delete_record.png -------------------------------------------------------------------------------- /docs/images/admin_delete_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_delete_request.png -------------------------------------------------------------------------------- /docs/images/admin_delete_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_delete_user.png -------------------------------------------------------------------------------- /docs/images/admin_edit_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_edit_note.png -------------------------------------------------------------------------------- /docs/images/admin_edit_prr_liaison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_edit_prr_liaison.png -------------------------------------------------------------------------------- /docs/images/admin_edit_qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_edit_qa.png -------------------------------------------------------------------------------- /docs/images/admin_edit_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_edit_request.png -------------------------------------------------------------------------------- /docs/images/admin_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_note.png -------------------------------------------------------------------------------- /docs/images/admin_prr_liaison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_prr_liaison.png -------------------------------------------------------------------------------- /docs/images/admin_qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_qa.png -------------------------------------------------------------------------------- /docs/images/admin_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_record.png -------------------------------------------------------------------------------- /docs/images/admin_remove_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_remove_record.png -------------------------------------------------------------------------------- /docs/images/admin_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/admin_request.png -------------------------------------------------------------------------------- /docs/images/all_request_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/all_request_table.png -------------------------------------------------------------------------------- /docs/images/answer_question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/answer_question.png -------------------------------------------------------------------------------- /docs/images/ask_question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/ask_question.png -------------------------------------------------------------------------------- /docs/images/change_point_of_contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/change_point_of_contact.png -------------------------------------------------------------------------------- /docs/images/city_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/city_url.png -------------------------------------------------------------------------------- /docs/images/close_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/close_request.png -------------------------------------------------------------------------------- /docs/images/extend_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/extend_request.png -------------------------------------------------------------------------------- /docs/images/filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/filter.png -------------------------------------------------------------------------------- /docs/images/generic_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/generic_request.png -------------------------------------------------------------------------------- /docs/images/liaisons-csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/liaisons-csv.png -------------------------------------------------------------------------------- /docs/images/new_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/new_request.png -------------------------------------------------------------------------------- /docs/images/not_city_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/not_city_record.png -------------------------------------------------------------------------------- /docs/images/offline_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/offline_record.png -------------------------------------------------------------------------------- /docs/images/offline_record_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/offline_record_example.png -------------------------------------------------------------------------------- /docs/images/public_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/public_filter.png -------------------------------------------------------------------------------- /docs/images/public_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/public_note.png -------------------------------------------------------------------------------- /docs/images/public_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/public_table.png -------------------------------------------------------------------------------- /docs/images/receipt_information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/receipt_information.png -------------------------------------------------------------------------------- /docs/images/reopen_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/reopen_request.png -------------------------------------------------------------------------------- /docs/images/responses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/responses.png -------------------------------------------------------------------------------- /docs/images/share_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/share_request.png -------------------------------------------------------------------------------- /docs/images/staff_csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/staff_csv.png -------------------------------------------------------------------------------- /docs/images/staff_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/staff_filter.png -------------------------------------------------------------------------------- /docs/images/staff_new_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/staff_new_request.png -------------------------------------------------------------------------------- /docs/images/staff_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/staff_table.png -------------------------------------------------------------------------------- /docs/images/status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/status.png -------------------------------------------------------------------------------- /docs/images/subscribe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/subscribe.png -------------------------------------------------------------------------------- /docs/images/track_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/track_request.png -------------------------------------------------------------------------------- /docs/images/unsubscribe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/unsubscribe.png -------------------------------------------------------------------------------- /docs/images/upload_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/docs/images/upload_record.png -------------------------------------------------------------------------------- /docs/local_installation.md: -------------------------------------------------------------------------------- 1 | ## Local installation 2 | 3 | We recommend you use a virtual environment to set up RecordTrac locally. More details can be found in [this howto](https://github.com/codeforamerica/howto/blob/master/Python-Virtualenv.md). 4 | 5 | 6 | This application requires [PostgreSQL](http://postgresapp.com/). For help getting started with PostgreSQL and creating a database, you can refer to [this howto](https://github.com/codeforamerica/howto/blob/master/PostgreSQL.md). Name your database 'recordtrac'. 7 | 8 | Inside your virtual environment: 9 | 10 | git clone git://github.com/codeforamerica/recordtrac.git 11 | cd recordtrac 12 | sudo pip install -r requirements.txt 13 | 14 | Save .env.example as .env, and update relevant fields in .env. 15 | 16 | ### Run locally 17 | 18 | If creating the database for the first time, run: 19 | 20 | foreman run python db_setup.py 21 | 22 | To seed the application with user data, requests and responses, run: 23 | 24 | foreman run python db_users.py 25 | foreman run python db_seed.py 26 | 27 | To use the application locally, run: 28 | 29 | foreman start 30 | 31 | 32 | You should see something similar to: 33 | 34 | 2013-05-06 12:16:53 [1776] [INFO] Starting gunicorn 0.17.4 35 | 2013-05-06 12:16:53 [1776] [INFO] Listening at: http://127.0.0.1:5000 (1776) 36 | 2013-05-06 12:16:53 [1776] [INFO] Using worker: sync 37 | 2013-05-06 12:16:53 [1779] [INFO] Booting worker with pid: 1779 38 | 2013-05-06 12:16:53 [1780] [INFO] Booting worker with pid: 1780 39 | 40 | Navigate to the url (in this case, http://127.0.0.1:5000) in your browser. 41 | 42 | #### Additional configuration 43 | 44 | For further information on configuring RecordTrac, refer to our [redeployment documentation](https://github.com/codeforamerica/recordtrac/blob/readme/docs/redeploy.md#redeploy). 45 | 46 | -------------------------------------------------------------------------------- /public_records_portal/RequestPresenter.py: -------------------------------------------------------------------------------- 1 | """ 2 | public_records_portal.RequestPresenter 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | Returns the html needed for the 'Request' portion of the case page. 6 | 7 | """ 8 | 9 | 10 | from public_records_portal import models 11 | from models import Note, QA 12 | from db_helpers import get_obj 13 | 14 | class RequestPresenter: 15 | def __init__(self, request, qa = None, note = None, index = None, public = False): 16 | self.index = index 17 | self.public = public 18 | self.request = request 19 | if qa: 20 | self.response = qa 21 | self.type = "qa" 22 | self.uid = self.response.owner_id 23 | self.staff = get_obj("User", self.uid) 24 | self.staff_email = "N/A" 25 | self.staff_department = "N/A" 26 | self.staff_phone = "N/A" 27 | self.staff_alias = "N/A" 28 | if self.staff: 29 | if self.staff.email: 30 | self.staff_email = self.staff.email 31 | if self.staff.department_id: 32 | self.staff_department = self.staff.department_id 33 | if self.staff.phone: 34 | self.staff_phone = self.staff.phone 35 | if self.staff.alias: 36 | self.staff_alias = self.staff.alias 37 | directory_popover = "directoryPopover('%s', '%s', '%s', '#contactinfoPopoverQA%s')" %(self.staff_email, self.staff_department, self.staff_phone, index) 38 | self.owner_link = '%s' % (self.response.owner_id, index, directory_popover, self.staff_alias or self.staff_email) 39 | self.icon = "icon-question icon-large" 40 | if note: 41 | self.response = note 42 | self.type = "note" 43 | self.icon = "icon-edit icon-large" 44 | 45 | def get_id(self): 46 | return self.response.id 47 | 48 | def display_text(self): 49 | if self.type == "qa": 50 | text = "%s - %s" %(self.response.question, self.owner_link) 51 | if self.response.answer: 52 | text = text + "

%s - Requester

" %(self.response.answer) 53 | else: 54 | if self.request.is_closed(): 55 | text = text + "

No response from requester

" 56 | else: 57 | if self.public: 58 | text = text + """ 59 |
60 | 61 | 62 | 63 |
64 | """ % (self.response.id, self.request.id) 65 | else: 66 | text = text + "

Requester hasn't answered yet.

" 67 | return text 68 | elif self.type == "note": 69 | return "%s - Requester" %(self.response.text) 70 | 71 | def get_icon(self): 72 | return self.icon 73 | 74 | def set_icon(self, icon): 75 | self.icon = icon 76 | 77 | def date(self): 78 | return self.response.date_created 79 | -------------------------------------------------------------------------------- /public_records_portal/ResponsePresenter.py: -------------------------------------------------------------------------------- 1 | """ 2 | public_records_portal.RequestPresenter 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | Returns the html needed for the 'Response' portion of the case page. 6 | 7 | """ 8 | 9 | 10 | 11 | from models import Record, Note 12 | 13 | class ResponsePresenter: 14 | def __init__(self, record = None, note = None): 15 | if record: 16 | self.response = record 17 | self.update_url = "update_a_record_delete" 18 | if self.response.access: 19 | self.type = "offline" 20 | elif self.response.doc_id: 21 | self.type = "document" 22 | else: 23 | self.type = "link" 24 | if note: 25 | self.response = note 26 | self.update_url = "update_a_note_delete" 27 | self.type = "note" 28 | if "Request extended:" in self.response.text: 29 | self.type = "extension" 30 | if self.type=="offline": 31 | self.icon = "icon-file-alt icon-large" 32 | elif self.type=="note": 33 | self.icon = "icon-edit icon-large" 34 | elif self.type=="link": 35 | self.icon = "icon-link icon-large" 36 | elif self.type =="document": 37 | self.icon = "icon-file-alt icon-large" 38 | elif self.type=="extension": 39 | self.icon = "icon-calendar icon-large" 40 | 41 | 42 | def get_update_url(self): 43 | return self.update_url 44 | 45 | def get_id(self): 46 | return self.response.id 47 | 48 | def uid(self): 49 | return self.response.user_id 50 | 51 | def staff_name(self): 52 | return get_attribute(attribute = "alias", obj_id = self.response.uid, obj_type = "User") 53 | 54 | def staff_dept(self): 55 | return get_attribute(attribute = "department", obj_id = self.response.uid, obj_type = "User") 56 | 57 | def staff_phone(self): 58 | return get_attribute(attribute = "phone", obj_id = self.response.uid, obj_type = "User") 59 | 60 | def staff_email(self): 61 | return get_attribute(attribute = "email", obj_id = self.response.uid, obj_type = "User") 62 | 63 | def display_text(self): 64 | if self.type == "offline": 65 | return "Name of Record: %s
How to Access Record: %s" %(self.response.description, self.response.access) 66 | elif self.type == "document": 67 | download_url = self.response.download_url 68 | return """ 69 | %(description)s 70 | """ %{"download_url": download_url, "description": self.response.description} 71 | elif self.type == "note": 72 | return self.response.text 73 | elif self.type == "link": 74 | return "%s " % (self.response.url, self.response.url, self.response.description) 75 | elif self.type == "extension": 76 | text = self.response.text.strip("Request extended:") 77 | return text 78 | 79 | def get_icon(self): 80 | return self.icon 81 | 82 | def set_icon(self, icon): 83 | self.icon = icon 84 | 85 | def date(self): 86 | return self.response.date_created 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /public_records_portal/__init__.py: -------------------------------------------------------------------------------- 1 | """A flask app to handle public records requests and display responses. 2 | 3 | Initializes application and all of its environment variables. 4 | 5 | .. moduleauthor:: Richa Agarwal 6 | 7 | """ 8 | 9 | import logging 10 | from os import environ 11 | from flask import Flask 12 | from flask.ext.sqlalchemy import SQLAlchemy 13 | 14 | # Initialize Flask app 15 | app = Flask(__name__) 16 | app.debug = True 17 | 18 | 19 | 20 | # Set environment variables 21 | def set_env(key, default = None): 22 | if key in environ: 23 | app.config[key] = environ[key] 24 | elif default: 25 | app.config[key] = default 26 | 27 | def set_bool_env(key, default = None): 28 | if key in environ: 29 | app.config[key] = environ[key].lower() in ('true', 'yes', 'on') 30 | elif default is not None: 31 | app.config[key] = default 32 | 33 | # UPDATES TO THESE DEFAULTS SHOULD OCCUR IN YOUR .env FILE. 34 | 35 | set_env(key = 'APPLICATION_URL', default = "http://127.0.0.1:5000/") 36 | set_env(key = 'ENVIRONMENT', default="LOCAL") 37 | # The default records liaison, to whom requests get routed to if no department is selected: 38 | set_env(key = 'DEFAULT_OWNER_EMAIL', default = 'recordtrac@codeforamerica.org') 39 | set_env(key = 'DEFAULT_OWNER_REASON', default = 'Open government coordinator' ) 40 | 41 | set_env(key = 'AGENCY_NAME', default = 'Your agency name') # e.g. City of Oakland 42 | set_env(key = 'SECRET_KEY', default = 'Change this to something super secret') # Flask application's secret key 43 | 44 | # The number of days an agency has (determined by law or policy) to fulfill a request 45 | # Currently due dates and overdue status is only showed to logged in agency staff 46 | set_env(key = 'DAYS_TO_FULFILL', default = '10') 47 | set_env(key = 'DAYS_AFTER_EXTENSION', default = '14') 48 | set_env(key = 'DAYS_UNTIL_OVERDUE', default = '2') 49 | 50 | set_env(key = 'TIMEZONE', default = "US/Pacific") 51 | 52 | # Set rest of the variables that don't have defaults: 53 | envvars = [ 54 | 'DEFAULT_MAIL_SENDER', # The e-mail address used as the FROM field for all notifications 55 | 'MAIL_USERNAME', # The SendGrid username 56 | 'MAIL_PASSWORD', # The SendGrid password 57 | 'SENDGRID_MONTHLY_LIMIT', # Your SendGrid Monthly Limit 58 | 'LIST_OF_ADMINS', # Defines who has admin access (/admin) with a comma delimited list of e-mail addresses. i.e. 'richa@codeforamerica.org,cris@codeforamerica.org' 59 | 'SECRET_KEY', # Flask app secret key 60 | 'AKISMET_KEY', # Used for spam filtering 61 | 'RECAPTCHA_PUBLIC_KEY', 62 | 'RECAPTCHA_PRIVATE_KEY', 63 | 'DEV_EMAIL', # Used for local e-mail testing if set 64 | 'CHECK_FOR_SPAM', # Used to test spam locally if set 65 | 'GOOGLE_FEEDBACK_FORM_ID', # The form ID that the feedback tab is hooked up to, 66 | 'STAFF_URL', # The path/URL at which a csv containing staff data lives. If this is not set, no one will be able to log into RecordTrac 67 | 'LIAISONS_URL', # The path/URL at which a csv containing liaisons/department data lives. If this is not set, initial request routing will always be directed to the default owner 68 | 'LOGO_ON_WHITE_URL', # The path/URL at which a logo (on a white background) of the agency is hosted. (.png or .jpg) 69 | 'LOGO_ON_BLACK_URL', # The path/URL at which a logo (on a black background) of the agency is hosted. (.png or .jpg) 70 | 'TESTING', # Set if you are running tests. Primarily used to ignore @login_requireds when running tests. 71 | 'SHOULD_UPLOAD' # Set if you want to test uploading documents to the specified host 72 | ] 73 | for envvar in envvars: 74 | set_env(key = envvar) 75 | 76 | # Database gets set slightly differently, to support difference between Flask and Heroku naming: 77 | app.config['SQLALCHEMY_DATABASE_URI'] = environ['DATABASE_URL'] 78 | 79 | # Initialize database 80 | db = SQLAlchemy(app) 81 | 82 | -------------------------------------------------------------------------------- /public_records_portal/akismet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | __version__ = "0.3" 4 | __date__ = "2005-12-01" 5 | __author__ = "David Lynch (kemayo AT Google's mail service DOT com)" 6 | __copyright__ = "Copyright 2005, David Lynch" 7 | __license__ = "New BSD" 8 | __history__ = """ 9 | 0.3 - 20051205 - Cleaned up __post. 10 | 0.2 - 20051201 - Added documentation, and tweaked the circumstances where an error 11 | will be thrown. 12 | 0.1 - 20051201 - Initial release. Everything pretty much works. Probably. 13 | """ 14 | 15 | import httplib 16 | from urllib import urlencode 17 | 18 | USERAGENT = "" 19 | AKISMET_URL = "rest.akismet.com" 20 | AKISMET_PORT = 80 21 | 22 | class AkismetError(Exception): 23 | def __init__(self, response, statuscode): 24 | self.response = response 25 | self.statuscode = statuscode 26 | def __str__(self): 27 | return repr(self.value) 28 | 29 | def __post(request, host, path, port = 80): 30 | connection = httplib.HTTPConnection(host, port) 31 | connection.request("POST", path, request, 32 | {"User-Agent":"%s | %s/%s" % (USERAGENT,"Akistmet.py", __version__), 33 | "Content-type":"application/x-www-form-urlencoded"}) 34 | response = connection.getresponse() 35 | 36 | return response.read(), response.status 37 | 38 | def verify_key(key, blog): 39 | """Find out whether a given WordPress.com API key is valid. 40 | Required parameters: 41 | key: A WordPress.com API key. 42 | blog: URL of the front page of the site comments will be submitted to. 43 | Returns True if a valid key, False if invalid. 44 | """ 45 | response, status = __post("key=%s&blog=%s" % (key,blog), AKISMET_URL, "/1.1/verify-key", AKISMET_PORT) 46 | 47 | if response == "valid": 48 | return True 49 | elif response == "invalid": 50 | return False 51 | else: 52 | raise AkismetError(response, status) 53 | 54 | def comment_check(key, blog, user_ip, user_agent, **other): 55 | """Submit a comment to find out whether Akismet thinks that it's spam. 56 | Required parameters: 57 | key: A valid WordPress.com API key, as tested with verify_key(). 58 | blog: URL of the front page of the site the comment will appear on. 59 | user_ip: IP address of the being which submitted the comment. 60 | user_agent: User agent reported by said being. 61 | Suggested "other" keys: "permalink", "referrer", "comment_type", "comment_author", 62 | "comment_author_email", "comment_author_url", "comment_content", and any other HTTP 63 | headers sent from the client. 64 | More detail on what should be submitted is available at: 65 | http://akismet.com/development/api/ 66 | 67 | Returns True if spam, False if ham. Throws an AkismetError if the server says 68 | anything unexpected. 69 | """ 70 | 71 | request = {'blog': blog, 'user_ip': user_ip, 'user_agent': user_agent} 72 | request.update(other) 73 | response, status = __post(urlencode(request), "%s.%s" % (key,AKISMET_URL), "/1.1/comment-check", AKISMET_PORT) 74 | 75 | if response == "true": 76 | return True 77 | elif response == "false": 78 | return False 79 | else: 80 | raise AkismetError(response, status) 81 | 82 | def submit_spam(key, blog, user_ip, user_agent, **other): 83 | """Report a false negative to Akismet. 84 | Same arguments as comment_check. 85 | Doesn't return anything. Throws an AkismetError if the server says anything. 86 | """ 87 | request = {'blog': blog, 'user_ip': user_ip, 'user_agent': user_agent} 88 | request.update(other) 89 | response, status = __post(urlencode(request), "%s.%s" % (key,AKISMET_URL), "/1.1/submit-spam", AKISMET_PORT) 90 | if status != 200 or response != "": 91 | raise AkismetError(response, status) 92 | 93 | def submit_ham(key, blog, user_ip, user_agent, **other): 94 | """Report a false positive to Akismet. 95 | Same arguments as comment_check. 96 | Doesn't return anything. Throws an AkismetError if the server says anything. 97 | """ 98 | request = {'blog': blog, 'user_ip': user_ip, 'user_agent': user_agent} 99 | request.update(other) 100 | response, status = __post(urlencode(request), "%s.%s" % (key,AKISMET_URL), "/1.1/submit-ham", AKISMET_PORT) 101 | if status != 200 or response != "": 102 | raise AkismetError(response, status) -------------------------------------------------------------------------------- /public_records_portal/csv_export.py: -------------------------------------------------------------------------------- 1 | """ 2 | public_records_portal.csv_export 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | Implements an export to CSV function (for staff only) for relevant database fields. 6 | 7 | """ 8 | 9 | from public_records_portal import models, db 10 | import csv 11 | 12 | def export(): 13 | records = models.Request.query.order_by(models.Request.id).all() 14 | db_headers = ['id', 'text', 'date_received', 'date_created', 'due_date', 'extended'] 15 | all_headers = ['Request ID', 'Request Text', 'Date Received', 'Date Created', 'Date Due', 'Extended?', 'Requester Name', 'Requester Phone', 'Department Name', 'Point of Contact', 'All staff involved', 'Status'] 16 | yield '\t'.join(all_headers) + '\n' 17 | for curr in records: 18 | row = [] 19 | for name in db_headers: 20 | if name == 'text': 21 | text = getattr(curr,'text') 22 | text = text.replace('\n', '').replace('\r', '').replace('\t', '') 23 | text = text.encode('utf8') 24 | # print text 25 | row.append(str(text)) 26 | continue 27 | row.append(str(getattr(curr,name))) 28 | row.append(str(curr.requester_name().encode('utf8'))) 29 | row.append(str(curr.requester_phone())) 30 | row.append(str(curr.department_name())) 31 | row.append(str(curr.point_person_name())) 32 | row.append(str(','.join(curr.all_owners()))) 33 | row.append(str(curr.solid_status(cron_job = True))) 34 | yield '\t'.join(row) + '\n' 35 | -------------------------------------------------------------------------------- /public_records_portal/filters.py: -------------------------------------------------------------------------------- 1 | """ 2 | public_records_portal.filters 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | Registers Jinja filters for the Flask application 6 | 7 | """ 8 | 9 | 10 | from public_records_portal import app, gravatar, prr, notifications, helpers, db_helpers 11 | # Register your filter here! 12 | 13 | app.jinja_env.filters['due_date'] = notifications.due_date 14 | app.jinja_env.filters['get_obj'] = db_helpers.get_obj 15 | app.jinja_env.filters['get_objs'] = db_helpers.get_objs 16 | app.jinja_env.filters['date_granular'] = helpers.date_granular 17 | app.jinja_env.filters['get_attribute'] = db_helpers.get_attribute 18 | app.jinja_env.filters['get_responses_chronologically'] = prr.get_responses_chronologically 19 | app.jinja_env.filters['get_request_data_chronologically'] = prr.get_request_data_chronologically 20 | app.jinja_env.filters['get_gravatar_url'] = gravatar.get_gravatar_url 21 | app.jinja_env.filters['date'] = helpers.date 22 | app.jinja_env.filters['format_datetime'] = helpers.format_datetime 23 | app.jinja_env.filters['explain_action'] = helpers.explain_action 24 | app.jinja_env.filters['tutorial'] = helpers.tutorial 25 | app.jinja_env.filters['new_lines'] = helpers.new_lines 26 | app.jinja_env.filters['display_staff_participant'] = helpers.display_staff_participant 27 | app.jinja_env.filters['timestamp'] = helpers.timestamp 28 | -------------------------------------------------------------------------------- /public_records_portal/gravatar.py: -------------------------------------------------------------------------------- 1 | """ 2 | public_records_portal.gravatar 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | Implements gravatar helper functions 6 | 7 | """ 8 | 9 | 10 | 11 | # import code for encoding urls and generating md5 hashes 12 | import urllib, hashlib 13 | 14 | 15 | def get_gravatar_url(email): 16 | email = email.lower() 17 | gravatar_url = "http://www.gravatar.com/avatar/" + hashlib.md5(email.lower()).hexdigest() + "?d=blank" 18 | return gravatar_url 19 | 20 | -------------------------------------------------------------------------------- /public_records_portal/helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | public_records_portal.helpers 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | Implements various helper functions for RecordTrac 6 | 7 | """ 8 | 9 | from datetime import datetime, timedelta 10 | import os 11 | from public_records_portal import app 12 | import json 13 | from jinja2 import Markup 14 | from db_helpers import * 15 | import notifications 16 | import pytz 17 | 18 | def localize(datetime_str): 19 | tz = pytz.timezone(app.config['TIMEZONE']) 20 | return datetime_str.replace(tzinfo=pytz.utc).astimezone(tz) # This appears to work in Heroku but not locally 21 | 22 | def date_granular(timestamp): 23 | if not timestamp: 24 | return None 25 | if type(timestamp) is not datetime: 26 | timestamp = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%f") 27 | delta = datetime.now() - timestamp 28 | days, hours, minutes, seconds = delta.days, delta.seconds//3600, delta.seconds//60, delta.seconds 29 | if days < 1: 30 | if hours > 1: 31 | return "%s hours ago" % hours 32 | elif minutes > 1: 33 | return "%s minutes ago" % minutes 34 | elif seconds > 1: 35 | return "%s seconds ago" % seconds 36 | else: 37 | return "Just now." 38 | elif days == 1: 39 | return "A day ago" 40 | elif days > 1: 41 | weeks = days//7 42 | if weeks < 1: 43 | return "%s days ago" % days 44 | if weeks == 1: 45 | return "A week ago" 46 | elif weeks > 1: 47 | return "%s weeks ago" % weeks 48 | 49 | 50 | def date(obj): 51 | """ Take a datetime or datetime-like object and return a formatted date. """ 52 | if not obj: 53 | return None 54 | try: 55 | return notifications.format_date(obj) 56 | except: # Not a datetime object 57 | return notifications.format_date(datetime.strptime(obj, "%Y-%m-%dT%H:%M:%S.%f")) 58 | 59 | def format_datetime(obj, format): 60 | """ Take a datetime or datetime-like object and return a formatted datetime string. """ 61 | if not obj: 62 | return None 63 | try: 64 | return localize(obj).strftime(format) 65 | except: # Not a datetime object 66 | date_obj = datetime.strptime(obj, "%Y-%m-%dT%H:%M:%S.%f") 67 | return localize(date_obj).strftime(format) 68 | 69 | def timestamp(obj): 70 | return localize(obj).strftime('%H:%M:%S') 71 | 72 | 73 | def tutorial(section): 74 | # Get filepath for actions.json 75 | tutorial_filepath = os.path.join(app.root_path, 'static/json/tutorial.json') 76 | tutorial_json = open(tutorial_filepath) 77 | json_data = json.load(tutorial_json) 78 | explanation = json_data[section] 79 | return explanation 80 | 81 | def explain_action(action, explanation_type = None): 82 | # Get filepath for actions.json 83 | actions_filepath = os.path.join(app.root_path, 'static/json/actions.json') 84 | action_json = open(actions_filepath) 85 | json_data = json.load(action_json) 86 | explanation = json_data[action] 87 | if explanation_type: 88 | return explanation[explanation_type] 89 | else: 90 | explanation_str = "" 91 | if 'What' in explanation: 92 | explanation_str = explanation_str + explanation['What'] 93 | if 'Who' in explanation: 94 | explanation_str = explanation_str + " " + explanation['Who'] 95 | if 'Action' in explanation: 96 | explanation_str = explanation_str + " " + explanation['Action'] 97 | return explanation_str.replace('*agency*', app.config['AGENCY_NAME']) 98 | 99 | 100 | def clean_text(value): 101 | return unicode(Markup(value).striptags()) 102 | 103 | def new_lines(value): 104 | value = value.replace('\n','this_is_a_new_line') 105 | value = clean_text(value) 106 | value = value.replace('this_is_a_new_line','
') 107 | return Markup(value) 108 | 109 | def display_staff_participant(owner, request): 110 | point_of_contact = request.point_person() 111 | if point_of_contact: 112 | if owner.id == request.point_person().id: 113 | return None 114 | staff = get_obj("User",owner.user_id) 115 | if not staff: 116 | return None 117 | if staff.alias: 118 | return staff.alias 119 | else: 120 | return staff.email 121 | 122 | -------------------------------------------------------------------------------- /public_records_portal/scribd_helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | public_records_portal.scribd_helpers 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | Implements functions to interact with Scribd API for RecordTrac 6 | 7 | """ 8 | 9 | 10 | import scribd 11 | from public_records_portal import app, models 12 | from timeout import timeout 13 | from werkzeug import secure_filename 14 | import tempfile 15 | 16 | 17 | def should_upload(): 18 | if app.config['ENVIRONMENT'] != 'LOCAL': 19 | return True 20 | elif 'UPLOAD_DOCS' in app.config: 21 | return True 22 | return False 23 | 24 | 25 | # These are the extensions that can be uploaded to Scribd.com: 26 | ALLOWED_EXTENSIONS = ['txt', 'pdf', 'doc', 'ps', 'rtf', 'epub', 'key', 'odt', 'odp', 'ods', 'odg', 'odf', 'sxw', 'sxc', 'sxi', 'sxd', 'ppt', 'pps', 'xls', 'zip', 'docx', 'pptx', 'ppsx', 'xlsx', 'tif', 'tiff'] 27 | 28 | 29 | def progress(bytes_sent, bytes_total): 30 | app.logger.info("Scribd upload in progress: %s of %s (%s%%)" % (bytes_sent, bytes_total, bytes_sent*100/bytes_total)) 31 | 32 | def upload(document, filename, API_KEY, API_SECRET, description): 33 | # Configure the Scribd API. 34 | scribd.config(API_KEY, API_SECRET) 35 | doc_id = None 36 | try: 37 | # Upload the document from a file. 38 | doc = scribd.api_user.upload( 39 | targetfile = document, 40 | name = filename, 41 | progress_callback=progress, 42 | req_buffer = tempfile.TemporaryFile() 43 | ) 44 | doc.description = description 45 | doc.save() 46 | doc_id = doc.id 47 | return doc_id 48 | except scribd.ResponseError, err: 49 | app.logger.info('Scribd failed: code=%d, error=%s' % (err.errno, err.strerror)) 50 | return err.strerror 51 | 52 | def get_scribd_download_url(doc_id, record_id = None): 53 | if not should_upload(): 54 | return None 55 | API_KEY = app.config['SCRIBD_API_KEY'] 56 | API_SECRET = app.config['SCRIBD_API_SECRET'] 57 | try: 58 | scribd.config(API_KEY, API_SECRET) 59 | doc = scribd.api_user.get(doc_id) 60 | doc_url = doc.get_download_url() 61 | if record_id: 62 | set_scribd_download_url(download_url = doc_url, record_id = record_id) 63 | return doc_url 64 | except: 65 | return None 66 | 67 | 68 | def set_scribd_download_url(download_url, record_id): 69 | update_obj('download_url', download_url, obj_type = 'Record', obj_id = record_id) 70 | 71 | def scribd_batch_download(): 72 | req = Request.query.all() 73 | for record in req.records: 74 | if record.download_url: 75 | urllib.urlretrieve(record.downlaod_url, "saved_records/%s" %(record.filename)) 76 | 77 | def make_public(doc_id, API_KEY, API_SECRET): 78 | scribd.config(API_KEY, API_SECRET) 79 | doc = scribd.api_user.get(doc_id) 80 | doc.access = 'public' 81 | doc.save() 82 | 83 | def make_private(doc_id, API_KEY, API_SECRET): 84 | scribd.config(API_KEY, API_SECRET) 85 | doc = scribd.api_user.get(doc_id) 86 | doc.access = 'private' 87 | doc.save() 88 | 89 | 90 | def update_descriptions(API_KEY, API_SECRET): 91 | scribd.config(API_KEY, API_SECRET) 92 | for doc in scribd.api_user.all(): 93 | record = models.Record.query.filter_by(doc_id = doc.id).first() 94 | if record: 95 | link_back = app.config['APPLICATION_URL'] + 'request/' + str(record.request_id) 96 | description = "This document was uploaded via RecordTrac in response to a public records request for the %s. You can view the original request here: %s" % ( app.config['AGENCY_NAME'], link_back) 97 | doc.description = description 98 | doc.save() 99 | app.logger.info("\n\nUpdated Scribd document %s's description to %s" %(doc.id, description)) 100 | 101 | 102 | 103 | @timeout(seconds=20) 104 | def upload_file(document, request_id): 105 | # Uploads file to scribd.com and returns doc ID. File can be accessed at scribd.com/doc/id 106 | if not should_upload(): 107 | return '1', None # Don't need to do real uploads locally 108 | if document: 109 | allowed = allowed_file(document.filename) 110 | if allowed[0]: 111 | filename = secure_filename(document.filename) 112 | link_back = app.config['APPLICATION_URL'] + 'request/' + str(request_id) 113 | doc_id = upload(document = document, filename = filename, API_KEY = app.config['SCRIBD_API_KEY'], API_SECRET = app.config['SCRIBD_API_SECRET'], description = "This document was uploaded via RecordTrac in response to a public records request for the %s. You can view the original request here: %s" % (app.config['AGENCY_NAME'], link_back)) 114 | return doc_id, filename 115 | else: 116 | return allowed # Returns false and extension 117 | return None, None 118 | 119 | ### @export "allowed_file" 120 | def allowed_file(filename): 121 | ext = filename.rsplit('.', 1)[1] 122 | return ext in ALLOWED_EXTENSIONS, ext 123 | -------------------------------------------------------------------------------- /public_records_portal/spam.py: -------------------------------------------------------------------------------- 1 | """ 2 | public_records_portal.spam 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | Implements spam filters used on RecordTrac's forms that don't require login. Akismet is a dependency (https://akismet.com) and the following environment variables need to be set in order for this to work: AKISMET_KEY, APPLICATION_URL 6 | 7 | """ 8 | 9 | 10 | from public_records_portal import app 11 | import akismet 12 | import logging 13 | from flask.ext.login import current_user 14 | 15 | def check_for_spam(): 16 | if current_user.is_authenticated(): # Spam filter is currently implemented to prevent bot spamming, so if someone is logged in they have already verified they are human 17 | return False 18 | if app.config['ENVIRONMENT'] == 'PRODUCTION': # This only needs to work in production, unless a local config variable is set to indicate otherwise 19 | return True 20 | elif 'CHECK_FOR_SPAM' in app.config: 21 | return True 22 | return False 23 | 24 | def is_spam(comment, user_ip, user_agent): 25 | if check_for_spam(): 26 | app.logger.info("\n\nAttempting to check for spam...") 27 | if not is_working_akismet_key(): 28 | app.logger.info("\n\nThere was a problem verifying the supplied AKISMET_KEY. Unable to check for spam.") 29 | return False 30 | if isinstance(comment, unicode): 31 | comment = comment.encode('utf8', 'ignore') 32 | if akismet.comment_check(key=app.config['AKISMET_KEY'], blog=app.config['APPLICATION_URL'], user_ip = user_ip, user_agent = user_agent, comment_content = comment) or 'http' in comment: 33 | app.logger.info("Spam detected: %s" % comment ) 34 | return True 35 | return False 36 | 37 | def is_working_akismet_key(): 38 | key = app.config['AKISMET_KEY'] 39 | blog = app.config['APPLICATION_URL'] 40 | return akismet.verify_key(key=key, blog=blog) 41 | -------------------------------------------------------------------------------- /public_records_portal/static/css/_response_widget.css: -------------------------------------------------------------------------------- 1 | /* custom css for _response_widget.html */ 2 | 3 | div.input-append { 4 | width: 530px; 5 | } 6 | 7 | h3.control-widget { 8 | display: inline !important; 9 | } 10 | 11 | .rw-container { 12 | /*border: 3px dashed green;*/ 13 | margin: 20px 0px; 14 | display: inline !important; 15 | } 16 | 17 | .rw-controller-container { 18 | /*text-align: center;*/ 19 | /*width: 500px;*/ 20 | display: inline; 21 | } 22 | 23 | .rw-controller-container .title { 24 | display: inline; 25 | margin-right: 5px; 26 | font-size: 26px; 27 | } 28 | 29 | .rw-controller-btns-container { 30 | /*box-shadow: inset 0px 0px 7px -1px #9CA9AD;*/ 31 | display: inline; 32 | border-radius: 4px; 33 | /*background-color: #fff;*/ 34 | /*border: 2px solid #08c;*/ 35 | color: #17B292; 36 | padding: 4px; 37 | margin: 5px; 38 | margin-left: 2em; 39 | } 40 | 41 | .plus-icon { 42 | display: inline; 43 | font-size: 1.4em; 44 | } 45 | 46 | .rw-btn-wrap, .rw-btn, .rw-btn-expand { 47 | display: inline-block; 48 | margin: 0 0px; 49 | padding: 2px; 50 | } 51 | 52 | .rw-btn-wrap { 53 | /*border-radius: 3px;*/ 54 | /*max-width: 20px;*/ 55 | /*white-space: nowrap;*/ 56 | /*overflow: hidden;*/ 57 | padding: 0px; 58 | /*-webkit-transition: max-width 1s;*/ 59 | /*opacity: .7;*/ 60 | margin-right: 1em; 61 | border-radius: 10px; 62 | padding-left: 5px; 63 | padding-right: 5px; 64 | } 65 | 66 | .rw-btn-wrap.active { 67 | /*box-shadow: inset 0px 0px 2px 1px #888;*/ 68 | /*background-color: rgb(234, 234, 255);*/ 69 | /*max-width: 100%;*/ 70 | font-size: 1.2em; 71 | opacity: 1; 72 | /*border-bottom: 2px solid #17B292;*/ 73 | /*color: #17B292;*/ 74 | color: #333; 75 | } 76 | 77 | .rw-btn-wrap:hover { 78 | /*background-color: blue;*/ 79 | cursor: hand; 80 | cursor: pointer; 81 | opacity: .9; 82 | /*max-width: 40%;*/ 83 | /*-webkit-transition: max-width 1s;*/ 84 | color: #333; 85 | background-color: rgba(23,178,146, .3); 86 | } 87 | 88 | .rw-actions-container { 89 | padding-top: 15px; 90 | } 91 | 92 | .rw-actions-container .default-text { 93 | text-align: center; 94 | font-size: 20px; 95 | } 96 | 97 | .rw-actions-container .target-for { 98 | display: inline-block; 99 | display: none; 100 | padding: 5px; 101 | /*background-color: rgb(234, 234, 255);*/ 102 | /*border: 1px dashed #ccc;*/ 103 | } 104 | 105 | #bounce { 106 | position: relative; 107 | } 108 | -------------------------------------------------------------------------------- /public_records_portal/static/css/about.css: -------------------------------------------------------------------------------- 1 | /* custom css for about.html */ 2 | 3 | /* bigger font for FAQ */ 4 | ul { 5 | /*font-size: 18px;*/ 6 | margin-left: 3em; 7 | } 8 | 9 | /* bold for table legend */ 10 | p.table-legend { 11 | font-weight: bold; 12 | margin-top: 1em; 13 | } 14 | 15 | /* room for explanation text */ 16 | p.table-legend-explanation { 17 | padding-left: 3em; 18 | } 19 | p.table-legend-explanation:first-child { 20 | margin-top: 1em; 21 | } 22 | 23 | 24 | #about_image { 25 | margin-top: 2%; 26 | margin-bottom: 2%; 27 | } 28 | 29 | /*img[alt="City of Oakland"] { 30 | margin-left: 5%; 31 | }*/ 32 | 33 | #about_image i { 34 | margin-left: 5%; 35 | margin-right: 5%; 36 | margin-top: 20%; 37 | line-height: 0.2em; 38 | } 39 | 40 | .bottombreathe { 41 | padding-bottom: 1%; 42 | } 43 | 44 | .footerbreathe { 45 | padding-bottom: 5%; 46 | } 47 | -------------------------------------------------------------------------------- /public_records_portal/static/css/admin.css: -------------------------------------------------------------------------------- 1 | .manage-hotlinks { 2 | margin-left: 2em; 3 | } 4 | 5 | p.intro_text { 6 | font-size: 16px; 7 | } -------------------------------------------------------------------------------- /public_records_portal/static/css/all_requests_noresults.css: -------------------------------------------------------------------------------- 1 | p.explanation1, p.explanation2 { 2 | font-size: 18px; 3 | } 4 | 5 | p.explanation1 { 6 | margin-top: 2em; 7 | } -------------------------------------------------------------------------------- /public_records_portal/static/css/base.css: -------------------------------------------------------------------------------- 1 | /* custom css for base.html and child templates */ 2 | 3 | /* larger logout link */ 4 | input#logout_form.btn.btn-link { 5 | font-size: 16px; 6 | font-weight: bold; 7 | } 8 | #logoutForm { 9 | margin-bottom: 0px; 10 | } 11 | 12 | /* scoot navbar start to the right */ 13 | a.brand { 14 | margin-left: 5% !important; 15 | } 16 | 17 | /* navbar layout tweaks due to insertion of Oakland tree logo */ 18 | /* tree logo */ 19 | img[id="logo_nav"] { 20 | margin-top: -5px; 21 | margin-right: 10px; 22 | } 23 | 24 | /* tabs */ 25 | ul.nav.tabs { 26 | font-size: 16px; 27 | } 28 | ul.nav.tabs li.active { 29 | border-bottom: 2px solid #17B292; 30 | } 31 | ul.nav.tabs li.active a { 32 | padding-bottom: 4px; 33 | } 34 | 35 | /* brand orange beta tag in navbar */ 36 | #beta { 37 | color: #FB991B; 38 | } 39 | 40 | /* placeholder title green */ 41 | a.brand { 42 | color: #17B292 !important; 43 | } 44 | 45 | /* some bootstrap stuff to create responsive container */ 46 | .container { 47 | margin-left: 0; 48 | } 49 | 50 | .navbar-inner { 51 | -webkit-border-radius: 0; 52 | -moz-border-radius: 0; 53 | border-radius: 0; 54 | } 55 | 56 | .footer { 57 | -webkit-border-radius: 0; 58 | -moz-border-radius: 0; 59 | border-radius: 0; 60 | } 61 | 62 | #rerouteForm { 63 | margin: 0 0 0px; 64 | } 65 | 66 | 67 | /* validation style */ 68 | /*label.valid { 69 | width: 24px; 70 | height: 24px; 71 | background: url(../images/valid.png) center center no-repeat; 72 | display: inline-block; 73 | text-indent: -9999px; 74 | }*/ 75 | label.error { 76 | font-weight: bold; 77 | color: red; 78 | padding: 2px 8px; 79 | margin-top: 2px; 80 | } 81 | 82 | /* tighter paragraphs in contact hover, request date */ 83 | .tight_paragraph { 84 | margin-bottom: 0; 85 | } 86 | 87 | /* new request form field explanations */ 88 | /*.request_field_explanation { 89 | margin-left: 5%; 90 | vertical-align: top; 91 | display: inline-block; 92 | max-width: 600px; 93 | color: #606060 ; 94 | }*/ 95 | 96 | /* all infotips appear as superscript */ 97 | .icon-info-sign { 98 | vertical-align: super; 99 | } 100 | 101 | /* tooltip text, left-aligned */ 102 | .tooltip-inner { 103 | text-align: left ; 104 | } 105 | 106 | 107 | 108 | 109 | 110 | /* ----- Sticky Footer ----- by Ryan Fait http://ryanfait.com/sticky-footer/ ----- */ 111 | * { 112 | margin: 0; 113 | } 114 | html, body { 115 | height: 100%; 116 | } 117 | .wrapper { 118 | min-height: 100%; 119 | height: auto !important; 120 | height: 100%; 121 | margin: 0 auto -5em; /* the bottom margin is the negative value of the footer's height */ 122 | } 123 | .footer, .push { 124 | height: 5em; /* .push must be the same height as .footer */ 125 | margin-bottom: 0px; /* custom: added to fix the Bootstrap well margins */ 126 | } 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /public_records_portal/static/css/bottom_sidebar.css: -------------------------------------------------------------------------------- 1 | /* custom css for bottom_sidebar */ 2 | 3 | .b_sidebar { 4 | margin-right: 1em; 5 | } -------------------------------------------------------------------------------- /public_records_portal/static/css/case.css: -------------------------------------------------------------------------------- 1 | /* custom css for case.html and child templates */ 2 | 3 | /* "requester" gray */ 4 | /*span.requester { 5 | font-style: italic; 6 | }*/ 7 | 8 | /* gives the responses a bit more white space between */ 9 | .response { 10 | padding-top: 2%; 11 | } 12 | 13 | /* bottom margin for page */ 14 | #responses-container, #participant-container { 15 | margin-bottom: 5em; 16 | } 17 | 18 | /* follow button area */ 19 | #whatisfollow { 20 | display: inline; 21 | margin-left: 0.25em; 22 | } 23 | div.input-append { 24 | display: inline; 25 | } 26 | div.follow { 27 | margin-bottom: 1em; 28 | } 29 | i.icon-question.icon-large { 30 | color: #666; 31 | } 32 | div.row-fluid.follow { 33 | margin-top: 1em; 34 | } 35 | 36 | /* PoC and Helper area */ 37 | h4.case_participantRow { 38 | margin-bottom: 0.25em; 39 | } 40 | 41 | div.participantRow-history { 42 | margin-top: 0.5em; 43 | margin-bottom: 1em; 44 | } 45 | /*div.participantRow { 46 | padding: 5px; 47 | }*/ 48 | 49 | /* change hover cursor for history popover */ 50 | a.historyPopover:hover { 51 | cursor: text; 52 | text-decoration: none; 53 | } 54 | 55 | /* deemphasize text and carets for request area truncation */ 56 | div.case-show-all { 57 | color: #666; 58 | } 59 | 60 | /* truncation bar for request area */ 61 | .case-show-all { 62 | background-color: #bdbdbd; 63 | text-align: center; 64 | margin: 0 -19px -19px -19px; 65 | padding: 3px 0; 66 | border-radius: 0 0 3px 3px; 67 | } 68 | .case-show-all:hover { 69 | background-color: #cdcdcd; 70 | cursor: hand; 71 | cursor: pointer; 72 | } 73 | 74 | /* general case page layout tweaks*/ 75 | h3 { 76 | line-height: 20px !important; 77 | } 78 | 79 | #status-text { 80 | margin-top: 5px; 81 | margin-bottom: 8px; 82 | } 83 | 84 | div.request-status-well { 85 | margin-bottom: 10px; 86 | padding: 0; 87 | } 88 | 89 | #responses-container { 90 | margin-top: 3em; 91 | } 92 | 93 | div.well { 94 | margin-bottom: 1em; 95 | } 96 | 97 | form { 98 | margin-bottom: 0; 99 | } 100 | 101 | #no-records { 102 | margin-bottom: 5em; 103 | margin-top: 0.5em; 104 | } 105 | 106 | /* _public_note.html */ 107 | div.button-margin { 108 | margin-bottom: 0.5em !important; 109 | } 110 | 111 | /* online document link icon */ 112 | i[class|="icon-external-link"] { 113 | vertical-align: super; 114 | font-size:10px; 115 | } 116 | 117 | /* move the response text closer to the response icon */ 118 | div.span9 {margin-left: 0px !important;} 119 | 120 | /* make history popover wider */ 121 | /*.historyPopover { 122 | 123 | }*/ 124 | 125 | /* contact info link */ 126 | span[class|="contactinfoPopover"] {color: #666666;} 127 | span[class|="timestampPopover"] {color: #666666;} 128 | 129 | /* info hover link */ 130 | .contactinfoPopover span {color: #3a87ad;} 131 | .timestampPopover span {color: #3a87ad;} 132 | 133 | /* public note section indent */ 134 | #public_note {margin-left: -5%;} -------------------------------------------------------------------------------- /public_records_portal/static/css/feedback.css: -------------------------------------------------------------------------------- 1 | /* custom css for feedback form in base.html */ 2 | 3 | #floating_link { 4 | position: fixed; 5 | left: -50px; 6 | top: 20em; 7 | display: block; 8 | width: 100px; 9 | height: 50px; 10 | background-color: #666; 11 | overflow: hidden; 12 | border-radius: 5px; 13 | color: white; 14 | text-align: center; 15 | writing-mode: bt-lr; 16 | -webkit-transform: rotate(270deg); 17 | -moz-transform: rotate(270deg); 18 | -ms-transform: rotate(270deg); 19 | -o-transform: rotate(270deg); 20 | transform: rotate(270deg); 21 | line-height : 70px; 22 | transition: left 0.25s; 23 | -webkit-transition: left 0.2s; 24 | } 25 | 26 | #floating_link:hover { 27 | text-decoration:none; 28 | left: -45px; 29 | background-color: #1CDBB4; 30 | } 31 | 32 | #floating_link i {color:white;} -------------------------------------------------------------------------------- /public_records_portal/static/css/landing.css: -------------------------------------------------------------------------------- 1 | /* custom css for landing.html and child templates */ 2 | 3 | /* disclaimer */ 4 | #disclaimer { 5 | margin-bottom: 1em; 6 | margin-top: 1em; 7 | } 8 | 9 | /* data header */ 10 | #recent-requests { 11 | margin-top:20px; 12 | margin-bottom:30px; 13 | } 14 | 15 | /* breathing room for landing footer */ 16 | #footer { 17 | padding-bottom: 2em; 18 | } 19 | 20 | 21 | /*hover states for images */ 22 | .round_link { 23 | border-radius: 20px; 24 | margin-bottom: 10%; 25 | padding-top: 5%; 26 | padding-bottom: 5%; 27 | } 28 | 29 | .round_link:hover { 30 | background-color: rgba(23,178,146, .3); 31 | color: #555; 32 | text-decoration: none; 33 | } 34 | 35 | 36 | /* brand area */ 37 | #logo_landing { 38 | /*display: inline-block; 39 | margin-top: -1.5em; 40 | margin-right: 1em;*/ 41 | float: right; 42 | margin-top: 2.25em; 43 | margin-bottom: 1em; 44 | width: 130px; 45 | 46 | } 47 | /*h3 { 48 | margin-top: 1em; 49 | }*/ 50 | 51 | /* expore, request, track content */ 52 | h4.text-center.text-title { 53 | font-size: 18px; 54 | margin-bottom: 0.25em; 55 | margin-top: 1em; 56 | color: #333; 57 | } 58 | p.text-center.text-body { 59 | font-size: 16px; 60 | color: #333; 61 | } 62 | 63 | h1 { 64 | color: #17b292; 65 | margin-top: 1em; 66 | /*display: inline-block;*/ 67 | } 68 | 69 | img[alt="request,track,explore"] { 70 | margin-top: 1em; 71 | /* aligns image with content */ 72 | padding-left: 35px; 73 | } 74 | 75 | .left-image { 76 | width: 75px; 77 | height: 95px; 78 | } 79 | .center-image { 80 | width: 100px; 81 | height: 91px; 82 | } 83 | .right-image { 84 | width: 150px; 85 | height: 90px; 86 | } 87 | 88 | /*div.span4 { 89 | margin-top: 1em; 90 | }*/ 91 | 92 | p.text-title { 93 | margin-bottom: 0px; 94 | } 95 | 96 | #seedata { 97 | margin-top: 1em; 98 | margin-bottom: 3em; 99 | } 100 | 101 | #startmeup { 102 | margin-bottom: 1em; 103 | } 104 | 105 | .center-image { 106 | display: block; 107 | margin-left: auto; 108 | margin-right: auto; 109 | } 110 | 111 | #request-count { 112 | background-color: #17b292; 113 | } 114 | 115 | /*i.icon-arrow-right { 116 | padding-left: 2px; 117 | font-size: 12pt; 118 | }*/ 119 | 120 | /* REQUESTS VIZ CSS --- */ 121 | /*.axis path, 122 | .axis line { 123 | fill: none; 124 | stroke: #000; 125 | shape-rendering: crispEdges; 126 | } 127 | 128 | .bar { 129 | fill: #999; 130 | } 131 | 132 | .bar:hover { 133 | fill: #FB991B ; 134 | } 135 | 136 | .no-line.axis path { 137 | display: none; 138 | } 139 | 140 | .axis text { 141 | font-size: 12px; 142 | text-anchor: left; 143 | } 144 | 145 | .no-line.axis { 146 | width: 50px; 147 | overflow: hidden; 148 | } 149 | 150 | .d3-tip { 151 | line-height: 1; 152 | font-weight: bold; 153 | padding: 12px; 154 | background: rgba(0, 0, 0, 0.8); 155 | color: #fff; 156 | border-radius: 2px; 157 | }*/ 158 | 159 | /* Creates a small triangle extender for the tooltip */ 160 | /*.d3-tip:after { 161 | box-sizing: border-box; 162 | display: inline; 163 | font-size: 10px; 164 | width: 100%; 165 | line-height: 1; 166 | color: rgba(0, 0, 0, 0.8); 167 | content: "\25BC"; 168 | position: absolute; 169 | text-align: center; 170 | }*/ 171 | 172 | /* Style northward tooltips differently */ 173 | /*.d3-tip.n:after { 174 | margin: -2px 0 0 0; 175 | top: 100%; 176 | left: 0; 177 | }*/ 178 | -------------------------------------------------------------------------------- /public_records_portal/static/css/manage_request_city.css: -------------------------------------------------------------------------------- 1 | /* custom css for manage_request_city.html and child templates */ 2 | 3 | /* _ask_question.html */ 4 | #control-question { 5 | margin-bottom: 0.5em; 6 | } 7 | 8 | /* PoC and Helper area */ 9 | a#contactinfoPopoverRoute { 10 | color: #666; 11 | } 12 | div.row-fluid.participantRow-poc{ 13 | padding-top: 3px; 14 | } 15 | h4.participantRow-title { 16 | color: #17B292; 17 | border-radius: 5px; 18 | padding: 5px; 19 | margin-bottom: 0px; 20 | } 21 | h4.participantRow-title:hover { 22 | background-color: rgba(23,178,146, .3); 23 | color: #555; 24 | /*color: #1CDBB4;*/ 25 | } 26 | div.participantRow-participants, div.participantRow-poc, div.participantRow-history { 27 | padding-left: 5px; 28 | padding-right: 5px; 29 | } 30 | 31 | i.icon-arrow-right, i.icon-plus { 32 | padding-right: 3px; 33 | } 34 | i.icon-user { 35 | padding-right: 3px; 36 | } 37 | .unassignPopover { 38 | border-radius: 5px; 39 | padding: 3px; 40 | margin-bottom: 0px; 41 | margin-left: -5px; 42 | } 43 | .unassignPopover:hover { 44 | background-color:#17b292; 45 | cursor: pointer; 46 | *cursor: hand; 47 | background-color: rgba(23,178,146, .3); 48 | color: #555; 49 | border-radius: 5px; 50 | padding: 3px; 51 | margin-bottom: 0px; 52 | } 53 | 54 | /*i.icon-arrow-right:hover { 55 | color: #888; 56 | } 57 | h4.partcipantRow-title { 58 | display: inline; 59 | }*/ 60 | /*h4.partcipantRow-title:hover { 61 | color: #00bb94; 62 | background-color: rgba(23,178,146, .3); 63 | }*/ 64 | #reroutePopover:hover, #notifyPopover:hover{ 65 | text-decoration: none; 66 | } 67 | /*div.participantRow-helpers { 68 | margin-top: 0.5em; 69 | } 70 | div.participantRow-history { 71 | margin-top: 0.5em; 72 | margin-bottom: 1em; 73 | }*/ 74 | 75 | 76 | 77 | /* Top Reponses (request area) */ 78 | .response.qa, .response.note { 79 | padding: 7px 5px; 80 | background-color: #fff; 81 | margin-bottom: 7px; 82 | border-radius: 3px; 83 | box-shadow: inset 0px 0px 5px #ccc; 84 | } 85 | .qa .qa-question { 86 | padding: 2px; 87 | background-color: rgba(176, 233, 255, 0.61); 88 | } 89 | .qa .qa-answer { 90 | padding: 2px; 91 | } 92 | 93 | /* Contact info area */ 94 | #no_email { 95 | padding-right: 5px; 96 | } 97 | 98 | /* Bottom Responses*/ 99 | .response-wrapper { 100 | border-bottom: 1px solid #ccc; 101 | margin-bottom: 10px; 102 | padding-top: 10px; 103 | } 104 | .response-wrapper:last-child { 105 | border-bottom: none; 106 | } 107 | .less, .more { 108 | color: #17B292; 109 | } 110 | .less:hover, .more:hover { 111 | cursor: hand; 112 | cursor: pointer; 113 | } 114 | 115 | /* Request status*/ 116 | .request-status-well { 117 | text-align: center; 118 | color: #fff; 119 | padding: 0; 120 | } 121 | 122 | .request-status-well.open { 123 | background-color: #2688AD; 124 | } 125 | 126 | .request-status-well.soon { 127 | background-color: #FB991B; 128 | } 129 | 130 | .request-status-well.overdue { 131 | background-color: #CA1A1A; 132 | } 133 | 134 | .request-status-well.closed { 135 | background-color: #2a2b2b; 136 | } 137 | 138 | -------------------------------------------------------------------------------- /public_records_portal/static/css/manage_request_city_less_js.css: -------------------------------------------------------------------------------- 1 | /* custom css for manage_request_city.html and child templates */ 2 | 3 | /* a bit of space between helpers */ 4 | #unassignForm { 5 | margin-bottom: 10px; 6 | } 7 | 8 | /* _ask_question.html */ 9 | #control-question { 10 | margin-bottom: 0.5em; 11 | } 12 | 13 | /* PoC and Helper area */ 14 | a#contactinfoPopoverRoute { 15 | color: #666; 16 | } 17 | div.row-fluid.participantRow-poc{ 18 | padding-top: 3px; 19 | } 20 | h4.participantRow-title { 21 | /*color: #17B292;*/ 22 | border-radius: 5px; 23 | padding: 5px; 24 | margin-bottom: 0px; 25 | } 26 | div.participantRow-participants, div.participantRow-poc, div.participantRow-history { 27 | padding-left: 5px; 28 | padding-right: 5px; 29 | } 30 | 31 | i.icon-arrow-right, i.icon-plus { 32 | padding-right: 3px; 33 | } 34 | i.icon-user { 35 | padding-right: 3px; 36 | } 37 | .unassignPopover { 38 | border-radius: 5px; 39 | padding: 3px; 40 | margin-bottom: 0px; 41 | margin-left: -5px; 42 | } 43 | /*.unassignPopover:hover { 44 | background-color:#17b292; 45 | cursor: pointer; 46 | *cursor: hand; 47 | background-color: rgba(23,178,146, .3); 48 | color: #555; 49 | border-radius: 5px; 50 | padding: 3px; 51 | margin-bottom: 0px; 52 | }*/ 53 | 54 | /*i.icon-arrow-right:hover { 55 | color: #888; 56 | } 57 | h4.partcipantRow-title { 58 | display: inline; 59 | }*/ 60 | /*h4.partcipantRow-title:hover { 61 | color: #00bb94; 62 | background-color: rgba(23,178,146, .3); 63 | }*/ 64 | #reroutePopover:hover, #notifyPopover:hover{ 65 | text-decoration: none; 66 | } 67 | /*div.participantRow-helpers { 68 | margin-top: 0.5em; 69 | } 70 | div.participantRow-history { 71 | margin-top: 0.5em; 72 | margin-bottom: 1em; 73 | }*/ 74 | 75 | 76 | 77 | /* Top Reponses (request area) */ 78 | .response.qa, .response.note { 79 | padding: 7px 5px; 80 | background-color: #fff; 81 | margin-bottom: 7px; 82 | border-radius: 3px; 83 | box-shadow: inset 0px 0px 5px #ccc; 84 | } 85 | .qa .qa-question { 86 | padding: 2px; 87 | background-color: rgba(176, 233, 255, 0.61); 88 | } 89 | .qa .qa-answer { 90 | padding: 2px; 91 | } 92 | 93 | /* Contact info area */ 94 | #no_email { 95 | padding-right: 5px; 96 | } 97 | 98 | /* Bottom Responses*/ 99 | .response-wrapper { 100 | border-bottom: 1px solid #ccc; 101 | margin-bottom: 10px; 102 | padding-top: 10px; 103 | } 104 | .response-wrapper:last-child { 105 | border-bottom: none; 106 | } 107 | .less, .more { 108 | color: #17B292; 109 | } 110 | .less:hover, .more:hover { 111 | cursor: hand; 112 | cursor: pointer; 113 | } 114 | 115 | /* Request status*/ 116 | .request-status-well { 117 | text-align: center; 118 | color: #fff; 119 | padding: 0; 120 | } 121 | 122 | .request-status-well.open { 123 | background-color: #2688AD; 124 | } 125 | 126 | .request-status-well.soon { 127 | background-color: #FB991B; 128 | } 129 | 130 | .request-status-well.overdue { 131 | background-color: #CA1A1A; 132 | } 133 | 134 | .request-status-well.closed { 135 | background-color: #2a2b2b; 136 | } 137 | 138 | -------------------------------------------------------------------------------- /public_records_portal/static/css/missing_email.css: -------------------------------------------------------------------------------- 1 | /* custom css for missing_email.html */ 2 | 3 | #missing-email { 4 | margin-top: 3em; 5 | } -------------------------------------------------------------------------------- /public_records_portal/static/css/new_request.css: -------------------------------------------------------------------------------- 1 | /* custom css for new_request.html and child templates */ 2 | 3 | /* all popovers fit size to content */ 4 | .helpPopover { 5 | width: 200px !important; 6 | } 7 | 8 | /* increase size of intro text */ 9 | p.intro_text { 10 | font-size: 16px; 11 | } 12 | 13 | /* tweaking the appearance of the frankenbox for dept dropdown */ 14 | input.combobox.input-block-level { 15 | margin-right: -2em; 16 | } 17 | 18 | .department_optional{ 19 | font-size: 12px; 20 | } 21 | 22 | #not_public_record{ 23 | /*border: 1px solid red;*/ 24 | } 25 | 26 | p.lead { 27 | margin-bottom: 5px; 28 | margin-top: 30px; 29 | } 30 | 31 | div.spacer { 32 | margin-bottom: 10px; 33 | } 34 | 35 | button[type] { 36 | padding-left: 2em; 37 | padding-right: 2em; 38 | } 39 | 40 | p.example { 41 | font-size: 17.5px; 42 | font-weight: 300; 43 | line-height: 1.25; 44 | } 45 | 46 | .popover { 47 | background-color: #dffbf5; 48 | } 49 | 50 | .popover-title { 51 | display: none; 52 | } 53 | 54 | .popover.top .arrow:after { 55 | border-top-color: #dffbf5; 56 | } 57 | 58 | .popover.right .arrow:after { 59 | border-right-color: #dffbf5; 60 | } 61 | 62 | .popover.bottom .arrow:after { 63 | border-bottom-color: #dffbf5; 64 | } 65 | 66 | .popover.left .arrow:after { 67 | border-left-color: #dffbf5; 68 | } 69 | 70 | .r_sidebar { 71 | margin-top: 4.25em; 72 | /*margin-right: 3em;*/ 73 | } 74 | 75 | /* gives the create request button a bit of breathing room */ 76 | #requestButton { 77 | margin-bottom: 20%; 78 | } 79 | -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/DT_bootstrap.css: -------------------------------------------------------------------------------- 1 | 2 | div.dataTables_length label { 3 | float: left; 4 | text-align: left; 5 | } 6 | 7 | div.dataTables_length select { 8 | width: 75px; 9 | } 10 | 11 | div.dataTables_filter label { 12 | float: right; 13 | } 14 | 15 | div.dataTables_info { 16 | padding-top: 8px; 17 | } 18 | 19 | div.dataTables_paginate { 20 | float: right; 21 | margin: 0; 22 | } 23 | 24 | table.table { 25 | clear: both; 26 | margin-bottom: 6px !important; 27 | max-width: none !important; 28 | } 29 | 30 | table.table thead .sorting, 31 | table.table thead .sorting_asc, 32 | table.table thead .sorting_desc, 33 | table.table thead .sorting_asc_disabled, 34 | table.table thead .sorting_desc_disabled { 35 | cursor: pointer; 36 | *cursor: hand; 37 | } 38 | 39 | table.table thead .sorting { background: url('../../images/plugins/dataTables/sort_both.png') no-repeat center right; } 40 | table.table thead .sorting_asc { background: url('../../images/plugins/dataTables/sort_asc.png') no-repeat center right; } 41 | table.table thead .sorting_desc { background: url('../../images/plugins/dataTables/sort_desc.png') no-repeat center right; } 42 | 43 | table.table thead .sorting_asc_disabled { background: url('../../images/plugins/dataTables/sort_asc_disabled.png') no-repeat center right; } 44 | table.table thead .sorting_desc_disabled { background: url('../../images/plugins/dataTables/sort_desc_disabled.png') no-repeat center right; } 45 | 46 | table.dataTable th:active { 47 | outline: none; 48 | } 49 | 50 | /* Scrolling */ 51 | div.dataTables_scrollHead table { 52 | margin-bottom: 0 !important; 53 | border-bottom-left-radius: 0; 54 | border-bottom-right-radius: 0; 55 | } 56 | 57 | div.dataTables_scrollHead table thead tr:last-child th:first-child, 58 | div.dataTables_scrollHead table thead tr:last-child td:first-child { 59 | border-bottom-left-radius: 0 !important; 60 | border-bottom-right-radius: 0 !important; 61 | } 62 | 63 | div.dataTables_scrollBody table { 64 | border-top: none; 65 | margin-bottom: 0 !important; 66 | } 67 | 68 | div.dataTables_scrollBody tbody tr:first-child th, 69 | div.dataTables_scrollBody tbody tr:first-child td { 70 | border-top: none; 71 | } 72 | 73 | div.dataTables_scrollFoot table { 74 | border-top: none; 75 | } 76 | 77 | 78 | 79 | 80 | /* 81 | * TableTools styles 82 | */ 83 | .table tbody tr.active td, 84 | .table tbody tr.active th { 85 | background-color: #08C; 86 | color: white; 87 | } 88 | 89 | .table tbody tr.active:hover td, 90 | .table tbody tr.active:hover th { 91 | background-color: #0075b0 !important; 92 | } 93 | 94 | .table-striped tbody tr.active:nth-child(odd) td, 95 | .table-striped tbody tr.active:nth-child(odd) th { 96 | background-color: #017ebc; 97 | } 98 | 99 | table.DTTT_selectable tbody tr { 100 | cursor: pointer; 101 | *cursor: hand; 102 | } 103 | 104 | div.DTTT .btn { 105 | color: #333 !important; 106 | font-size: 12px; 107 | } 108 | 109 | div.DTTT .btn:hover { 110 | text-decoration: none !important; 111 | } 112 | 113 | 114 | ul.DTTT_dropdown.dropdown-menu a { 115 | color: #333 !important; /* needed only when demo_page.css is included */ 116 | } 117 | 118 | ul.DTTT_dropdown.dropdown-menu li:hover a { 119 | background-color: #0088cc; 120 | color: white !important; 121 | } 122 | 123 | /* TableTools information display */ 124 | div.DTTT_print_info.modal { 125 | height: 150px; 126 | margin-top: -75px; 127 | text-align: center; 128 | } 129 | 130 | div.DTTT_print_info h6 { 131 | font-weight: normal; 132 | font-size: 28px; 133 | line-height: 28px; 134 | margin: 1em; 135 | } 136 | 137 | div.DTTT_print_info p { 138 | font-size: 14px; 139 | line-height: 20px; 140 | } 141 | 142 | 143 | 144 | /* 145 | * FixedColumns styles 146 | */ 147 | div.DTFC_LeftHeadWrapper table, 148 | div.DTFC_LeftFootWrapper table, 149 | table.DTFC_Cloned tr.even { 150 | background-color: white; 151 | } 152 | 153 | div.DTFC_LeftHeadWrapper table { 154 | margin-bottom: 0 !important; 155 | border-top-right-radius: 0 !important; 156 | border-bottom-left-radius: 0 !important; 157 | border-bottom-right-radius: 0 !important; 158 | } 159 | 160 | div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child, 161 | div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child { 162 | border-bottom-left-radius: 0 !important; 163 | border-bottom-right-radius: 0 !important; 164 | } 165 | 166 | div.DTFC_LeftBodyWrapper table { 167 | border-top: none; 168 | margin-bottom: 0 !important; 169 | } 170 | 171 | div.DTFC_LeftBodyWrapper tbody tr:first-child th, 172 | div.DTFC_LeftBodyWrapper tbody tr:first-child td { 173 | border-top: none; 174 | } 175 | 176 | div.DTFC_LeftFootWrapper table { 177 | border-top: none; 178 | } 179 | 180 | -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/bootstrap-combobox.css: -------------------------------------------------------------------------------- 1 | .combobox-container { 2 | margin-bottom: 5px; 3 | *zoom: 1; 4 | } 5 | .combobox-container:before, 6 | .combobox-container:after { 7 | display: table; 8 | content: ""; 9 | } 10 | .combobox-container:after { 11 | clear: both; 12 | } 13 | .combobox-container input, 14 | .combobox-container .uneditable-input { 15 | -webkit-border-radius: 0 3px 3px 0; 16 | -moz-border-radius: 0 3px 3px 0; 17 | border-radius: 0 3px 3px 0; 18 | } 19 | .combobox-container input:focus, 20 | .combobox-container .uneditable-input:focus { 21 | position: relative; 22 | z-index: 2; 23 | } 24 | .combobox-container .uneditable-input { 25 | border-left-color: #ccc; 26 | } 27 | .combobox-container .add-on { 28 | float: left; 29 | display: inline-block; 30 | width: auto; 31 | min-width: 16px; 32 | height: inherit !important; 33 | margin-right: -1px; 34 | padding: 4px 5px; 35 | font-weight: normal; 36 | color: #999999; 37 | text-align: center; 38 | text-shadow: 0 1px 0 #ffffff; 39 | background-color: #f5f5f5; 40 | border: 1px solid #ccc; 41 | -webkit-border-radius: 3px 0 0 3px; 42 | -moz-border-radius: 3px 0 0 3px; 43 | border-radius: 3px 0 0 3px; 44 | 45 | } 46 | .combobox-container .active { 47 | background-color: #a9dba9; 48 | border-color: #46a546; 49 | } 50 | .combobox-container input, 51 | .combobox-container .uneditable-input { 52 | float: left; 53 | -webkit-border-radius: 3px 0 0 3px; 54 | -moz-border-radius: 3px 0 0 3px; 55 | border-radius: 3px 0 0 3px; 56 | } 57 | .combobox-container .uneditable-input { 58 | border-left-color: #eee; 59 | border-right-color: #ccc; 60 | } 61 | .combobox-container .add-on { 62 | margin-right: 0; 63 | margin-left: -1px; 64 | -webkit-border-radius: 0 3px 3px 0; 65 | -moz-border-radius: 0 3px 3px 0; 66 | border-radius: 0 3px 3px 0; 67 | } 68 | .combobox-container input:first-child { 69 | *margin-left: -160px; 70 | } 71 | .combobox-container input:first-child + .add-on { 72 | *margin-left: -21px; 73 | } 74 | .combobox-container select { 75 | display: inline-block; 76 | width: 0; 77 | height: 0; 78 | border: 0; 79 | padding: 0; 80 | margin: 0; 81 | text-indent: -99999px; 82 | *text-indent: 0; 83 | } 84 | .form-search .combobox-container, 85 | .form-inline .combobox-container { 86 | display: inline-block; 87 | margin-bottom: 0; 88 | vertical-align: top; 89 | } 90 | .form-search .combobox-container .add-on, 91 | .form-inline .combobox-container .add-on { 92 | vertical-align: middle; 93 | } 94 | .combobox-selected .combobox-clear { 95 | display: inline-block; 96 | } 97 | .combobox-selected .caret { 98 | display: none; 99 | } 100 | .combobox-clear { 101 | display: none; 102 | width: 14px; 103 | height: 14px; 104 | line-height: 14px; 105 | vertical-align: top; 106 | opacity: 0.3; 107 | filter: alpha(opacity=30); 108 | } 109 | .dropdown:hover .combobox-clear, 110 | .open.dropdown .combobox-clear { 111 | opacity: 1; 112 | filter: alpha(opacity=100); 113 | } 114 | .btn .combobox-clear { 115 | margin-top: 1px; 116 | margin-left: 1px; 117 | } 118 | .btn:hover .combobox-clear, 119 | .open.btn-group .combobox-clear { 120 | opacity: 1; 121 | filter: alpha(opacity=100); 122 | } 123 | .typeahead-long { 124 | max-height: 300px; 125 | overflow-y: auto; 126 | } 127 | .control-group.error .combobox-container .add-on { 128 | color: #B94A48; 129 | border-color: #B94A48; 130 | } 131 | .control-group.error .combobox-container .caret { 132 | border-top-color: #B94A48; 133 | } 134 | .control-group.warning .combobox-container .add-on { 135 | color: #C09853; 136 | border-color: #C09853; 137 | } 138 | .control-group.warning .combobox-container .caret { 139 | border-top-color: #C09853; 140 | } 141 | .control-group.success .combobox-container .add-on { 142 | color: #468847; 143 | border-color: #468847; 144 | } 145 | .control-group.success .combobox-container .caret { 146 | border-top-color: #468847; 147 | } 148 | .btn .combobox-clear [class^="icon-"] { 149 | line-height: 1.4em; 150 | } -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/bootstrap-fileupload.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v2.3.1-j6 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world by @mdo and @fat, extended by @ArnoldDaniels. 9 | */ 10 | .clearfix { 11 | *zoom: 1; 12 | } 13 | .clearfix:before, 14 | .clearfix:after { 15 | display: table; 16 | content: ""; 17 | line-height: 0; 18 | } 19 | .clearfix:after { 20 | clear: both; 21 | } 22 | .hide-text { 23 | font: 0/0 a; 24 | color: transparent; 25 | text-shadow: none; 26 | background-color: transparent; 27 | border: 0; 28 | } 29 | .input-block-level { 30 | display: block; 31 | width: 100%; 32 | min-height: 30px; 33 | -webkit-box-sizing: border-box; 34 | -moz-box-sizing: border-box; 35 | box-sizing: border-box; 36 | } 37 | .btn-file { 38 | overflow: hidden; 39 | position: relative; 40 | vertical-align: middle; 41 | } 42 | .btn-file > input { 43 | position: absolute; 44 | top: 0; 45 | right: 0; 46 | margin: 0; 47 | opacity: 0; 48 | filter: alpha(opacity=0); 49 | transform: translate(-300px, 0) scale(4); 50 | font-size: 23px; 51 | direction: ltr; 52 | cursor: pointer; 53 | } 54 | .fileupload { 55 | margin-bottom: 9px; 56 | } 57 | .fileupload .uneditable-input { 58 | display: inline-block; 59 | margin-bottom: 0px; 60 | vertical-align: middle; 61 | cursor: text; 62 | } 63 | .fileupload .thumbnail { 64 | overflow: hidden; 65 | display: inline-block; 66 | margin-bottom: 5px; 67 | vertical-align: middle; 68 | text-align: center; 69 | } 70 | .fileupload .thumbnail > img { 71 | display: inline-block; 72 | vertical-align: middle; 73 | max-height: 100%; 74 | } 75 | .fileupload .btn { 76 | vertical-align: middle; 77 | } 78 | .fileupload-exists .fileupload-new, 79 | .fileupload-new .fileupload-exists { 80 | display: none; 81 | } 82 | .fileupload-inline .fileupload-controls { 83 | display: inline; 84 | } 85 | .fileupload-new .input-append .btn-file { 86 | -webkit-border-radius: 0 3px 3px 0; 87 | -moz-border-radius: 0 3px 3px 0; 88 | border-radius: 0 3px 3px 0; 89 | } 90 | .thumbnail-borderless .thumbnail { 91 | border: none; 92 | padding: 0; 93 | -webkit-border-radius: 0; 94 | -moz-border-radius: 0; 95 | border-radius: 0; 96 | -webkit-box-shadow: none; 97 | -moz-box-shadow: none; 98 | box-shadow: none; 99 | } 100 | .fileupload-new.thumbnail-borderless .thumbnail { 101 | border: 1px solid #ddd; 102 | } 103 | .control-group.warning .fileupload .uneditable-input { 104 | color: #a47e3c; 105 | border-color: #a47e3c; 106 | } 107 | .control-group.warning .fileupload .fileupload-preview { 108 | color: #a47e3c; 109 | } 110 | .control-group.warning .fileupload .thumbnail { 111 | border-color: #a47e3c; 112 | } 113 | .control-group.error .fileupload .uneditable-input { 114 | color: #b94a48; 115 | border-color: #b94a48; 116 | } 117 | .control-group.error .fileupload .fileupload-preview { 118 | color: #b94a48; 119 | } 120 | .control-group.error .fileupload .thumbnail { 121 | border-color: #b94a48; 122 | } 123 | .control-group.success .fileupload .uneditable-input { 124 | color: #468847; 125 | border-color: #468847; 126 | } 127 | .control-group.success .fileupload .fileupload-preview { 128 | color: #468847; 129 | } 130 | .control-group.success .fileupload .thumbnail { 131 | border-color: #468847; 132 | } 133 | -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/bootstrap-select.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * bootstrap-select v1.1.2 3 | * http://silviomoreto.github.io/bootstrap-select/ 4 | * 5 | * Copyright 2013 bootstrap-select 6 | * Licensed under the MIT license 7 | */.bootstrap-select.btn-group,.bootstrap-select.btn-group[class*="span"]{float:none;display:inline-block;margin-bottom:10px;margin-left:0}.form-search .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group{margin-bottom:0}.bootstrap-select.btn-group.pull-right,.bootstrap-select.btn-group[class*="span"].pull-right,.row-fluid .bootstrap-select.btn-group[class*="span"].pull-right{float:right}.input-append .bootstrap-select.btn-group{margin-left:-1px}.input-prepend .bootstrap-select.btn-group{margin-right:-1px}.bootstrap-select:not([class*="span"]){width:220px}.bootstrap-select{width:220px \0/}.bootstrap-select>.btn{width:100%}.error .bootstrap-select .btn{border:1px solid #b94a48}.bootstrap-select.show-menu-arrow.open>.btn{z-index:1001}.bootstrap-select .btn:focus{outline:thin dotted #333 !important;outline:5px auto -webkit-focus-ring-color !important;outline-offset:-2px}.bootstrap-select.btn-group .btn .filter-option{overflow:hidden;position:absolute;left:12px;right:25px;text-align:left}.bootstrap-select.btn-group .btn .caret{position:absolute;top:50%;right:12px;margin-top:-2px}.bootstrap-select.btn-group>.disabled,.bootstrap-select.btn-group .dropdown-menu li.disabled>a{cursor:not-allowed}.bootstrap-select.btn-group>.disabled:focus{outline:none !important}.bootstrap-select.btn-group[class*="span"] .btn{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;border:0;padding:0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu dt{display:block;padding:3px 20px;cursor:default}.bootstrap-select.btn-group .div-contain{overflow:hidden}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li>a.opt{padding-left:35px}.bootstrap-select.btn-group .dropdown-menu li>a{cursor:pointer}.bootstrap-select.btn-group .dropdown-menu li>dt small{font-weight:normal}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a i.check-mark{display:inline-block;position:absolute;right:15px;margin-top:2.5px}.bootstrap-select.btn-group .dropdown-menu li a i.check-mark{display:none}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu li:not(.disabled)>a:hover small,.bootstrap-select.btn-group .dropdown-menu li:not(.disabled)>a:focus small{color:#64b1d8;color:rgba(255,255,255,0.4)}.bootstrap-select.btn-group .dropdown-menu li>dt small{font-weight:normal}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #CCC;border-bottom-color:rgba(0,0,0,0.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid white;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after{display:block}.mobile-device{position:absolute;top:0;left:0;display:block !important;width:100%;height:100% !important;opacity:0} 8 | -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/bootstro.css: -------------------------------------------------------------------------------- 1 | /* custom */ 2 | h5 { 3 | color: black !important; 4 | } 5 | span.label.label-success { 6 | background-color: #2688AD; 7 | } 8 | h3.popover-title { 9 | color: black; 10 | font-weight: bold; 11 | } 12 | span.contactinfo { 13 | color:#666; 14 | } 15 | /* original */ 16 | 17 | .bootstro-next-btn 18 | { 19 | float:right; 20 | } 21 | .bootstro-prev-btn 22 | { 23 | float:left; 24 | } 25 | .bootstro-backdrop 26 | { 27 | opacity: 0.5; 28 | background-color: #000000; 29 | bottom: 0; 30 | left: 0; 31 | position: fixed; 32 | right: 0; 33 | top: 0; 34 | z-index: 1; 35 | filter: alpha(opacity = 50); 36 | } 37 | .bootstro-highlight 38 | { 39 | z-index: 9999; 40 | position: relative; 41 | border: 1px solid white; 42 | background-color: white; 43 | } 44 | 45 | .bootstro-finish-btn-wrapper 46 | { 47 | /*border-top:1px solid #eee;*/ 48 | padding-top:10px; 49 | text-align:center; 50 | clear:both; 51 | } 52 | .bootstro-nav-wrapper 53 | { 54 | margin-top:10px; 55 | } 56 | 57 | .bootstro-nav-wrapper button, .bootstro-finish-btn-wrapper button 58 | { 59 | font-size:12px !important; 60 | padding : 3px 5px 3px 5px !important; 61 | font-weight:bold; 62 | } 63 | 64 | 65 | -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/font/FontAwesome.otf -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /public_records_portal/static/css/plugins/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/css/plugins/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /public_records_portal/static/css/recaptcha.css: -------------------------------------------------------------------------------- 1 | 2 | #recaptcha_message { 3 | padding-top: 2em; 4 | } 5 | 6 | #recaptcha { 7 | padding-top: 1em; 8 | } 9 | 10 | button { 11 | margin-top: 1em; 12 | } -------------------------------------------------------------------------------- /public_records_portal/static/css/sidebar.css: -------------------------------------------------------------------------------- 1 | /* CHOOSE EITHER NORMAL OR FIXED SIDEBAR, NOT BOTH */ 2 | 3 | /* normal sidebar */ 4 | .r_sidebar { 5 | margin-top: 8.75em; 6 | margin-right: 2em; 7 | } 8 | 9 | /*.r_sidebar_banner { 10 | margin-top: 4.25em; 11 | margin-right: 1em; 12 | }*/ 13 | 14 | 15 | /* fixed right sidebar */ 16 | /*.r_sidebar-fixed { 17 | position:fixed; 18 | top:17.25%; 19 | width:19%; 20 | } 21 | 22 | @media (max-width: 767px) { 23 | .r_sidebar-fixed { 24 | width:auto; 25 | } 26 | } 27 | 28 | @media (max-width: 979px) { 29 | .r_sidbar-fixed { 30 | position:static; 31 | width: auto; 32 | } 33 | }*/ -------------------------------------------------------------------------------- /public_records_portal/static/css/signup.css: -------------------------------------------------------------------------------- 1 | /* custom css for signup.html */ 2 | 3 | #intro { 4 | height: 100% !important; 5 | -webkit-box-shadow: 0.1em 0 0.5em #615850; 6 | -moz-box-shadow: 0.1em 0 0.5em #615850; 7 | -ms-box-shdaow: 0.1em 0 0.5em #615850; 8 | -o-box-shadow: 0.1em 0 0.5em #615850; 9 | box-shadow: 0.1em 0 0.5em #615850; 10 | padding: 3% 3% 10% 4%; 11 | background-color: #6EC7B9; 12 | 13 | /*background-image: -ms-linear-gradient(top, #FFFFFF 0%, #009ce6 50%); 14 | background-image: -moz-linear-gradient(top, #FFFFFF 0%, #009ce6 50%); 15 | background-image: -o-linear-gradient(top, #FFFFFF 0%, #009ce6 50%); 16 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #FFFFFF), color-stop(3, #009ce6)); 17 | background-image: -webkit-linear-gradient(top, #FFFFFF 0%, #009ce6 50%); 18 | background-image: linear-gradient(to bottom, #FFFFFF 0%, #009ce6 50%);*/ 19 | } 20 | 21 | h1 a { 22 | color: white; 23 | display: inline-block; 24 | } 25 | 26 | h1 a:hover { 27 | color: white; 28 | text-decoration: none; 29 | } 30 | 31 | #intro p { 32 | color: black; 33 | font-family: 'Lato'; 34 | } 35 | 36 | 37 | #maincontainer { 38 | padding: 0 0 0 0; 39 | min-height: 100% !important; 40 | } 41 | 42 | img#flagtag_white { 43 | margin-bottom: 3em; 44 | display: block; 45 | margin-left: auto; 46 | margin-right: auto; 47 | 48 | } 49 | 50 | img#rtlogo { 51 | padding-right: 0.25em; 52 | padding-bottom: 0.25em; 53 | display: inline-block; 54 | } 55 | 56 | #maincontainer { 57 | overflow: hidden; 58 | .... 59 | } 60 | 61 | #intro { 62 | margin-bottom: -5000px; 63 | padding-bottom: 5000px; 64 | } 65 | 66 | #flagtag_white { 67 | margin-top: 2em; 68 | } 69 | 70 | a.text-center { 71 | font-weight: bold; 72 | color: white; 73 | } 74 | p { 75 | font-weight: bold; 76 | font-size: 16px; 77 | } 78 | 79 | blockquote { 80 | margin-top: 1em; 81 | } -------------------------------------------------------------------------------- /public_records_portal/static/css/social_media.css: -------------------------------------------------------------------------------- 1 | /* custom css for social media buttons in case.html */ 2 | 3 | #floating_media { 4 | position: fixed; 5 | left: 5px; 6 | top: 12.5em; 7 | display: block; 8 | width: 15px; 9 | line-height: 2.5em; 10 | } 11 | 12 | 13 | i.icon-twitter, i.icon-facebook { 14 | color: #666; 15 | } 16 | 17 | i.icon-twitter:hover, i.icon-facebook:hover { 18 | color: #1CDBB4; 19 | text-decoration: none; 20 | } -------------------------------------------------------------------------------- /public_records_portal/static/css/staff_card.css: -------------------------------------------------------------------------------- 1 | span.title, span.entry { 2 | font-size: 16px; 3 | } 4 | 5 | span.entry { 6 | font-weight: bold; 7 | } 8 | 9 | img.staffpic { 10 | margin-bottom: 1em; 11 | margin-top: 1.5em; 12 | } 13 | 14 | div.span3 { 15 | padding-left: 0; 16 | margin-left: 0; 17 | } 18 | 19 | div.row-fluid { 20 | margin-top: 3em; 21 | } -------------------------------------------------------------------------------- /public_records_portal/static/examples/liaisons.csv: -------------------------------------------------------------------------------- 1 | department name,PRR liaison,PRR backup 2 | Engineering,richa@codeforamerica.org,cris@codeforamerica.org 3 | Design,cris@codeforamerica.org, -------------------------------------------------------------------------------- /public_records_portal/static/examples/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/examples/logo.png -------------------------------------------------------------------------------- /public_records_portal/static/examples/logo_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/examples/logo_black.png -------------------------------------------------------------------------------- /public_records_portal/static/examples/staff.csv: -------------------------------------------------------------------------------- 1 | name,email,department name,phone number 2 | Richa Agarwal,richa@codeforamerica.org,Engineering,555-555-5555 3 | Cris Cristina,cris@codeforamerica.org,Design, -------------------------------------------------------------------------------- /public_records_portal/static/images/CfA_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/CfA_logo.png -------------------------------------------------------------------------------- /public_records_portal/static/images/CityTree_logo_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/CityTree_logo_green.png -------------------------------------------------------------------------------- /public_records_portal/static/images/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/create.png -------------------------------------------------------------------------------- /public_records_portal/static/images/explore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/explore.png -------------------------------------------------------------------------------- /public_records_portal/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/favicon.ico -------------------------------------------------------------------------------- /public_records_portal/static/images/flagtag_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/flagtag_white.png -------------------------------------------------------------------------------- /public_records_portal/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/logo.png -------------------------------------------------------------------------------- /public_records_portal/static/images/logo_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/logo_black.png -------------------------------------------------------------------------------- /public_records_portal/static/images/recordtrac_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/recordtrac_logo.png -------------------------------------------------------------------------------- /public_records_portal/static/images/recordtrac_logo_nomargin_whttxt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/recordtrac_logo_nomargin_whttxt.png -------------------------------------------------------------------------------- /public_records_portal/static/images/track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/track.png -------------------------------------------------------------------------------- /public_records_portal/static/images/valid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/recordtrac/794d22278782573f0b84f0b5fb22477e39a5cc42/public_records_portal/static/images/valid.png -------------------------------------------------------------------------------- /public_records_portal/static/js/_response_widget.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | var $rw_wrap = $('.rw-container'); 4 | 5 | $rw_wrap.on('click', '.rw-btn-wrap', function(e){ 6 | $container = $(e.delegateTarget); 7 | $this = $(this); 8 | target = $this.data('target'); 9 | if ($this.hasClass('active')) { 10 | $this.removeClass('active'); 11 | $container.find('[data-target-for="'+target+'"]').hide('400'); 12 | } else { 13 | // active tab background transitions 14 | $container.find('.rw-btn-wrap').each(function(){ 15 | $(this).removeClass('active'); 16 | }); 17 | $this.addClass('active'); 18 | // toggle content (data-target) 19 | $container.find('[data-target-for]').each(function(){ 20 | $(this).hide(); 21 | }); 22 | $container.find('[data-target-for="'+target+'"]').show('400'); 23 | } 24 | }); 25 | 26 | 27 | })($); 28 | -------------------------------------------------------------------------------- /public_records_portal/static/js/all_requests.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | Modernizr.load({ 3 | test: Modernizr.inputtypes.date, 4 | nope: ['http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.7/jquery-ui.min.js', 'jquery-ui.css'], 5 | complete: function () { 6 | $('input[type=date]').datepicker({ 7 | dateFormat: 'yy-mm-dd' 8 | }); 9 | } 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /public_records_portal/static/js/case.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | $('#load-more-qa').on('click', function() { 4 | $.getJSON('/api/request/2', function(data) { 5 | var html = ''; 6 | var qas = data.qas; 7 | for (var i = 0; i < qas.length; i++) { 8 | var qa = qas[i]; 9 | html += qa_to_html(qa); 10 | } 11 | $('#load-more-qa').before(html); 12 | }); 13 | }); 14 | 15 | function qa_to_html (qa) { 16 | return '

' + qa.date_created+ '

'; 17 | } 18 | 19 | // hide/show responses 20 | var $responses = $('.response'); 21 | hideExcept([0], $responses); 22 | 23 | // hides elements, can take a whitelist of indexes 24 | // ex: to hide all but first -> 25 | // hideExcept([0], $elem); 26 | function hideExcept(whitelist, elem) { 27 | $.each(elem, 28 | function(i, el) { 29 | if ($.inArray(i,whitelist) == -1) { 30 | $(el).hide(); 31 | } 32 | }); 33 | } 34 | 35 | $('.case-show-all').on('click',function() { 36 | var $this = $(this); 37 | if ($(this).hasClass('show')) { 38 | $this.toggleClass('show'); 39 | $responses.show(200); 40 | $this.html(' See less ') 41 | } else { 42 | $this.toggleClass('show'); 43 | hideExcept([0], $responses); 44 | $this.html(' See all ') 45 | } 46 | }); 47 | 48 | })($); 49 | -------------------------------------------------------------------------------- /public_records_portal/static/js/custom.bootstrap-select.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $('.selectpicker').selectpicker(); 3 | }); -------------------------------------------------------------------------------- /public_records_portal/static/js/custom.js: -------------------------------------------------------------------------------- 1 | 2 | // $(document).ready(function(){ 3 | /* validates public add a note form */ 4 | $("#public_note").validate({ 5 | rules: { 6 | note_text: { 7 | required: true, 8 | minlength: 2 9 | } 10 | }, 11 | highlight: function(element) { 12 | $(element).closest('.control-group').removeClass('success').addClass('error'); 13 | }, 14 | success: function(element) { 15 | element 16 | // .text('OK!').addClass('valid') 17 | .closest('.control-group').removeClass('error').addClass('success'); 18 | } 19 | }); 20 | // }); 21 | 22 | /* banner dismissal */ 23 | $(function(){$(".alert").alert()}) 24 | 25 | /* navbar active page indicator */ 26 | $(function(){ 27 | function stripTrailingSlash(str) { 28 | if(str.substr(-1) == '/') { 29 | return str.substr(0, str.length - 1); 30 | } 31 | return str; 32 | } 33 | 34 | var url = window.location.pathname; 35 | var activePage = stripTrailingSlash(url); 36 | 37 | $('.nav li a').each(function(){ 38 | var currentPage = stripTrailingSlash($(this).attr('href')); 39 | 40 | if (activePage == currentPage) { 41 | $(this).parent().addClass('active'); 42 | } 43 | }); 44 | }); 45 | 46 | /* timestamp popover */ 47 | $('.timestampPopover').popover({ 48 | trigger: 'hover', 49 | html : true, 50 | content: function() { 51 | return $(this).parent().find('.timestampPopover-content').html() 52 | } 53 | }); 54 | 55 | 56 | /* form in reroute popover */ 57 | $('#reroutePopover').popover({ 58 | html : true, 59 | title: function() { 60 | return $("#reroutePopover-head").html(); 61 | }, 62 | content: function() { 63 | return $("#reroutePopover-content").html(); 64 | } 65 | }); 66 | 67 | /* form in notify popover */ 68 | $('#notifyPopover').popover({ 69 | html : true, 70 | title: function() { 71 | return $("#notifyPopover-head").html(); 72 | }, 73 | content: function() { 74 | return $("#notifyPopover-content").html(); 75 | } 76 | }); 77 | 78 | /* form in history popover */ 79 | $('.historyPopover').popover({ 80 | trigger: 'hover', 81 | html : true, 82 | title: function() { 83 | return $("#historyPopover-head").html(); 84 | }, 85 | content: function() { 86 | return $("#historyPopover-content").html(); 87 | } 88 | }); 89 | 90 | function directoryPopover(staffName, staffDept, staffPhone, elemID){ 91 | if (!staffPhone) { 92 | staffPhone = 'Not available'; 93 | } 94 | /* all popovers */ 95 | $(elemID).popover({ 96 | trigger: 'hover', 97 | html : true, 98 | title: function() { 99 | return $("Contact information"); 100 | }, 101 | content: function() { 102 | var $cont = $("

Department: "+staffDept+"

Email: "+staffName+"

Phone: "+staffPhone+"

"); 103 | return $cont; 104 | } 105 | }); 106 | } 107 | 108 | 109 | /* tooptip */ 110 | $('[rel=tooltip]').tooltip({html:true}) 111 | 112 | /* need to include a way to close the popover by clicking anywhere outside the popover - 113 | currently have to click on button */ 114 | // $('rel["clickover"]').clickover(); 115 | 116 | /* table sort */ 117 | // $(document).ready(function() { 118 | // $('#allrequestTable').dataTable(); 119 | // } ); 120 | -------------------------------------------------------------------------------- /public_records_portal/static/js/date_picker.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | 3 | $("input[id^='datepicker']").datepicker(); 4 | var currentDate = new Date(); 5 | currentDay = (currentDate.getMonth()+1) + '/' + currentDate.getDate() + '/' + currentDate.getFullYear(); 6 | $("input[id^='datepicker']").val(currentDay); 7 | 8 | }); -------------------------------------------------------------------------------- /public_records_portal/static/js/new_request.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | 3 | $.validator.addMethod('nopostdate', function(value, element) { 4 | var eod = new Date(); 5 | var dateval = new Date(value); 6 | 7 | eod.setHours(23, 59, 59, 999); 8 | return this.optional(element) || dateval <= eod; 9 | }, "This field must not be postdated."); 10 | 11 | /* validates add a request form */ 12 | $("#submitRequest").validate({ 13 | rules: { 14 | request_text: { 15 | required: true, 16 | minlength: 2 17 | }, 18 | date_received: { 19 | nopostdate: true 20 | } 21 | }, 22 | messages: { 23 | date_received: "You must select a date in the past." 24 | }, 25 | highlight: function(element) { 26 | $(element).closest('.control-group').removeClass('success').addClass('error'); 27 | }, 28 | success: function(element) { 29 | element 30 | .closest('.control-group').removeClass('error').addClass('success'); 31 | } 32 | }); 33 | 34 | 35 | /* help text popover */ 36 | $('#requestTextarea').popover({ 37 | trigger: 'focus', 38 | html : true, 39 | content: function() { 40 | return $("#requestPopover-content").html(); 41 | } 42 | }); 43 | 44 | $('#inputEmail').popover({ 45 | trigger: 'focus', 46 | html : true, 47 | content: function() { 48 | return $("#emailPopover-content").html(); 49 | } 50 | }); 51 | 52 | $('#inputName').popover({ 53 | trigger: 'focus', 54 | html : true, 55 | content: function() { 56 | return $("#namePopover-content").html(); 57 | } 58 | }); 59 | 60 | $('#inputPhone').popover({ 61 | trigger: 'focus', 62 | html : true, 63 | content: function() { 64 | return $("#phonePopover-content").html(); 65 | } 66 | }); 67 | 68 | 69 | 70 | /* End of .ready functionality */ 71 | 72 | 73 | $('#requestTextarea').on('blur', function() { 74 | request_text = $(this).val(); 75 | request = $.ajax({ 76 | url: '/is_public_record', 77 | type: 'post', 78 | data: { 79 | request_text: request_text 80 | } 81 | }); 82 | request.done( function(data) { 83 | $div = $('#not_public_record'); 84 | if (data != '') { 85 | $div.addClass('alert').addClass('alert-error'); 86 | $div.html(data); 87 | $div.prepend(" "); 88 | } else { 89 | $div.empty(); 90 | $div.removeClass('alert').removeClass('alert-error'); 91 | } 92 | }); 93 | }) 94 | }); 95 | 96 | -------------------------------------------------------------------------------- /public_records_portal/static/js/placeholder.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | 3 | $('input, textarea').placeholder(); 4 | 5 | }); -------------------------------------------------------------------------------- /public_records_portal/static/js/plugins/DT_bootstrap.js: -------------------------------------------------------------------------------- 1 | /* Table initialisation */ 2 | $(document).ready(function() { 3 | 4 | /* adds placeholder text for search box */ 5 | $('.dataTables_filter label input').attr( { 6 | "placeholder": "I'd like to find...", 7 | }); 8 | } ); 9 | 10 | 11 | /* Default class modification */ 12 | $.extend( $.fn.dataTableExt.oStdClasses, { 13 | "sWrapper": "dataTables_wrapper form-inline" 14 | } ); 15 | 16 | 17 | /* API method to get paging information */ 18 | $.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings ) 19 | { 20 | return { 21 | "iStart": oSettings._iDisplayStart, 22 | "iEnd": oSettings.fnDisplayEnd(), 23 | "iLength": oSettings._iDisplayLength, 24 | "iTotal": oSettings.fnRecordsTotal(), 25 | "iFilteredTotal": oSettings.fnRecordsDisplay(), 26 | "iPage": oSettings._iDisplayLength === -1 ? 27 | 0 : Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ), 28 | "iTotalPages": oSettings._iDisplayLength === -1 ? 29 | 0 : Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength ) 30 | }; 31 | }; 32 | 33 | 34 | /* Bootstrap style pagination control */ 35 | $.extend( $.fn.dataTableExt.oPagination, { 36 | "bootstrap": { 37 | "fnInit": function( oSettings, nPaging, fnDraw ) { 38 | var oLang = oSettings.oLanguage.oPaginate; 39 | var fnClickHandler = function ( e ) { 40 | e.preventDefault(); 41 | if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) { 42 | fnDraw( oSettings ); 43 | } 44 | }; 45 | 46 | $(nPaging).addClass('pagination').append( 47 | '' 51 | ); 52 | var els = $('a', nPaging); 53 | $(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler ); 54 | $(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler ); 55 | }, 56 | 57 | "fnUpdate": function ( oSettings, fnDraw ) { 58 | var iListLength = 5; 59 | var oPaging = oSettings.oInstance.fnPagingInfo(); 60 | var an = oSettings.aanFeatures.p; 61 | var i, ien, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2); 62 | 63 | if ( oPaging.iTotalPages < iListLength) { 64 | iStart = 1; 65 | iEnd = oPaging.iTotalPages; 66 | } 67 | else if ( oPaging.iPage <= iHalf ) { 68 | iStart = 1; 69 | iEnd = iListLength; 70 | } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) { 71 | iStart = oPaging.iTotalPages - iListLength + 1; 72 | iEnd = oPaging.iTotalPages; 73 | } else { 74 | iStart = oPaging.iPage - iHalf + 1; 75 | iEnd = iStart + iListLength - 1; 76 | } 77 | 78 | for ( i=0, ien=an.length ; i'+j+'') 86 | .insertBefore( $('li:last', an[i])[0] ) 87 | .bind('click', function (e) { 88 | e.preventDefault(); 89 | oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength; 90 | fnDraw( oSettings ); 91 | } ); 92 | } 93 | 94 | // Add / remove disabled classes from the static elements 95 | if ( oPaging.iPage === 0 ) { 96 | $('li:first', an[i]).addClass('disabled'); 97 | } else { 98 | $('li:first', an[i]).removeClass('disabled'); 99 | } 100 | 101 | if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) { 102 | $('li:last', an[i]).addClass('disabled'); 103 | } else { 104 | $('li:last', an[i]).removeClass('disabled'); 105 | } 106 | } 107 | } 108 | } 109 | } ); 110 | 111 | 112 | /* 113 | * TableTools Bootstrap compatibility 114 | * Required TableTools 2.1+ 115 | */ 116 | if ( $.fn.DataTable.TableTools ) { 117 | // Set the classes that TableTools uses to something suitable for Bootstrap 118 | $.extend( true, $.fn.DataTable.TableTools.classes, { 119 | "container": "DTTT btn-group", 120 | "buttons": { 121 | "normal": "btn", 122 | "disabled": "disabled" 123 | }, 124 | "collection": { 125 | "container": "DTTT_dropdown dropdown-menu", 126 | "buttons": { 127 | "normal": "", 128 | "disabled": "disabled" 129 | } 130 | }, 131 | "print": { 132 | "info": "DTTT_print_info modal" 133 | }, 134 | "select": { 135 | "row": "active" 136 | } 137 | } ); 138 | 139 | // Have the collection use a bootstrap compatible dropdown 140 | $.extend( true, $.fn.DataTable.TableTools.DEFAULTS.oTags, { 141 | "collection": { 142 | "container": "ul", 143 | "button": "li", 144 | "liner": "a" 145 | } 146 | } ); 147 | } 148 | -------------------------------------------------------------------------------- /public_records_portal/static/js/plugins/accessifyhtml5.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Accessifyhtml5.js 3 | * 4 | * Source: https://github.com/yatil/accessifyhtml5.js 5 | */ 6 | 7 | var AccessifyHTML5=function(e,f){var c={article:{role:"article"},aside:{role:"complementary"},nav:{role:"navigation"},main:{role:"main"},output:{"aria-live":"polite"},section:{role:"region"},"[required]":{"aria-required":"true"}},g={ok:[],warn:[],fail:[]},k=g.fail,b,h,a,d,n,p,l,r,m,s=RegExp("aria-[a-z]+|role|tabindex|title|alt|data-[\\w-]+|lang|style|maxlength|placeholder|pattern|required|type|target|accesskey|longdesc"),t=0,q=document;if(q.querySelectorAll){e&&(e.header&&(c[e.header]={role:"banner"}), 8 | e.footer&&(c[e.footer]={role:"contentinfo"}),e.main&&(c[e.main]={role:"main"},c.main={role:""}));if(f&&f._CONFIG_&&f._CONFIG_.ignore_defaults)c=f;else for(a in f)c[a]=f[a];for(b in c)if(!b.match(/^_(CONFIG|[A-Z]+)_/)&&c.hasOwnProperty(b)){try{h=q.querySelectorAll(b)}catch(u){k.push({sel:b,attr:null,val:null,msg:"Invalid syntax for `document.querySelectorAll` function",ex:u})}p=c[b];(!h||1>h.length)&&g.warn.push({sel:b,attr:null,val:null,msg:"Not found"});for(l=0;l<'span6'f>r>t<'row'<'span6'i><'span6'p>>", 5 | "sPaginationType": "bootstrap", 6 | "aaSorting": [[ 1, "desc" ]], // 2nd column sort on # 7 | "aoColumnDefs": [ 8 | { "bSortable": false, "aTargets": [ 0 ] } 9 | ], 10 | "iDisplayLength": 50, 11 | "oLanguage": { 12 | "sLengthMenu": "_MENU_ records per page" 13 | } 14 | } ); 15 | /* adds placeholder text for search box */ 16 | $('.dataTables_filter label input').attr( { 17 | "placeholder": "I'd like to find...", 18 | // "class": "text-right", 19 | }); 20 | } ); -------------------------------------------------------------------------------- /public_records_portal/static/js/plugins/custom.bootstro.js: -------------------------------------------------------------------------------- 1 | /* calls bootstro (tutorial) plugin */ 2 | $(document).ready(function(){ 3 | // $("#tutorial").click(function(){ 4 | bootstro.start(".bootstro", { 5 | onComplete : function(params) 6 | { 7 | // alert("Reached end of introduction with total " + (params.idx + 1)+ " slides"); 8 | }, 9 | onExit : function(params) 10 | { 11 | // alert("Introduction stopped at slide #" + (params.idx + 1)); 12 | }, 13 | // }); 14 | }); 15 | // $(".demo_stopOn").click(function(){ 16 | // alert('Clicking on the backdrop or Esc will NOT stop the show') 17 | // bootstro.start('.bootstro', {stopOnBackdropClick : false, stopOnEsc:false}); 18 | // }); 19 | // $(".demo_size1").click(function(){ 20 | // bootstro.start('.bootstro_size1'); 21 | // }); 22 | // $(".demo_nonav").click(function(){ 23 | // bootstro.start('.bootstro', { 24 | // nextButton : '', 25 | // prevButton : '', 26 | // finishButton : '' 27 | // }); 28 | // }); 29 | // $(".demo_ajax").click(function(){ 30 | // bootstro.start('', { 31 | // url : './bootstro.json', 32 | // }); 33 | // }); 34 | }); -------------------------------------------------------------------------------- /public_records_portal/static/js/plugins/jquery.ellipsis.js: -------------------------------------------------------------------------------- 1 | /*! jQuery ellipsis - v1.0.8 - 2013-06-23 2 | * https://github.com/STAR-ZERO/jquery-ellipsis 3 | * Copyright (c) 2013 Kenji Abe; Licensed MIT */ 4 | 5 | (function($) { 6 | $.fn.ellipsis = function(options) { 7 | 8 | // デフォルトオプション 9 | var defaults = { 10 | 'row' : 1, // 省略行数 11 | 'char' : '...' // 省略文字 12 | }; 13 | 14 | options = $.extend(defaults, options); 15 | 16 | this.each(function() { 17 | // 現在のテキストを取得 18 | var $this = $(this); 19 | var text = $this.text(); 20 | var origHeight = $this.height(); 21 | 22 | // 1行分の高さを取得 23 | $this.text('a'); 24 | var rowHeight = $this.height(); 25 | var targetHeight = rowHeight * options.row; 26 | 27 | if (origHeight <= targetHeight) { 28 | $this.text(text); 29 | return; 30 | } 31 | 32 | // Binary search for max length 33 | var start = 1; 34 | var end = text.length; 35 | 36 | while (start < end) { 37 | var length = Math.ceil((start + end) / 2); 38 | 39 | $this.text(text.slice(0, length) + options['char']); 40 | 41 | if ($this.height () <= targetHeight) { 42 | start = length; 43 | } else { 44 | end = length - 1; 45 | } 46 | } 47 | 48 | $this.text(text.slice(0, start) + options['char']); 49 | }); 50 | 51 | return this; 52 | }; 53 | }) (jQuery); 54 | -------------------------------------------------------------------------------- /public_records_portal/static/js/plugins/rem.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module: rem - v1.3.2 3 | * Description: A polyfill to parse CSS links and rewrite pixel equivalents into head for non supporting browsers 4 | * Date Built: 2014-07-02 5 | * Copyright (c) 2014 | Chuck Carpenter ,Lucas Serven ; 6 | **/ 7 | !function(a){"use strict";var b=function(){var a=document.createElement("div");return a.style.cssText="font-size: 1rem;",/rem/.test(a.style.fontSize)},c=function(){for(var a=document.getElementsByTagName("link"),b=[],c=0;c0?(r=[],q=[],n=[],d()):g()}},f=function(a,b){for(var c,d=k(a).replace(/\/\*[\s\S]*?\*\//g,""),e=/[\w\d\s\-\/\\\[\]:,.'"*()<>+~%#^$_=|@]+\{[\w\d\s\-\/\\%#:!;,.'"*()]+\d*\.?\d+rem[\w\d\s\-\/\\%#:!;,.'"*()]*\}/g,f=d.match(e),g=/\d*\.?\d+rem/g,h=d.match(g),i=/(.*\/)/,j=i.exec(b)[0],l=/@import (?:url\()?['"]?([^'\)"]*)['"]?\)?[^;]*/gm;null!==(c=l.exec(a));)n.push(j+c[1]);null!==f&&0!==f.length&&(o=o.concat(f),p=p.concat(h))},g=function(){for(var a=/[\w\d\s\-\/\\%#:,.'"*()]+\d*\.?\d+rem[\w\d\s\-\/\\%#:!,.'"*()]*[;}]/g,b=0;b0){var a=l,f,e=s.substring(0,e).replace(H,i);if(e==i||e.charAt(e.length-1)==o)e+="*";try{f=t(e)}catch(k){}if(f){e=0;for(c=f.length;e-1&&(a=a.substring(0,l));if(a.charAt(0)==":")switch(a.slice(1)){case "root":c=function(a){return b?a!=p:a==p};break;case "target":if(m==8){c=function(a){function c(){var d=location.hash,e=d.slice(1);return b?d==i||a.id!=e:d!=i&&a.id==e}k(j,"hashchange",function(){g(a,d,c())});return c()};break}return!1;case "checked":c=function(a){J.test(a.type)&&k(a,"propertychange",function(){event.propertyName=="checked"&&g(a,d,a.checked!==b)});return a.checked!==b};break;case "disabled":b=!b;case "enabled":c=function(c){if(K.test(c.tagName))return k(c,"propertychange",function(){event.propertyName=="$disabled"&&g(c,d,c.a===b)}),q.push(c),c.a=c.disabled,c.disabled===b;return a==":enabled"?b:!b};break;case "focus":e="focus",f="blur";case "hover":e||(e="mouseenter",f="mouseleave");c=function(a){k(a,b?f:e,function(){g(a,d,!0)});k(a,b?e:f,function(){g(a,d,!1)});return b};break;default:if(!L.test(a))return!1}return{className:d,b:c}}function w(a){return M+"-"+(m==6&&N?O++:a.replace(P,function(a){return a.charCodeAt(0)}))}function D(a){return a.replace(x,h).replace(Q,o)}function g(a,c,d){var b=a.className,c=u(b,c,d);if(c!=b)a.className=c,a.parentNode.className+=i}function u(a,c,d){var b=RegExp("(^|\\s)"+c+"(\\s|$)"),e=b.test(a);return d?e?a:a+o+c:e?a.replace(b,h).replace(x,h):a}function k(a,c,d){a.attachEvent("on"+c,d)}function r(a,c){if(/^https?:\/\//i.test(a))return c.substring(0,c.indexOf("/",8))==a.substring(0,a.indexOf("/",8))?a:null;if(a.charAt(0)=="/")return c.substring(0,c.indexOf("/",8))+a;var d=c.split(/[?#]/)[0];a.charAt(0)!="?"&&d.charAt(d.length-1)!="/"&&(d=d.substring(0,d.lastIndexOf("/")+1));return d+a}function y(a){if(a)return n.open("GET",a,!1),n.send(),(n.status==200?n.responseText:i).replace(R,i).replace(S,function(c,d,b,e,f){return y(r(b||f,a))}).replace(T,function(c,d,b){d=d||i;return" url("+d+r(b,a)+d+") "});return i}function U(){var a,c;a=f.getElementsByTagName("BASE");for(var d=a.length>0?a[0].href:f.location.href,b=0;b0&&setInterval(function(){for(var a=0,c=q.length;a8||!n)){var z={NW:"*.Dom.select",MooTools:"$$",DOMAssistant:"*.$",Prototype:"$$",YAHOO:"*.util.Selector.query",Sizzle:"*",jQuery:"*",dojo:"*.query"},t,q=[],O=0,N=!0,M="slvzr",R=/(\/\*[^*]*\*+([^\/][^*]*\*+)*\/)\s*/g,S=/@import\s*(?:(?:(?:url\(\s*(['"]?)(.*)\1)\s*\))|(?:(['"])(.*)\3))[^;]*;/g,T=/\burl\(\s*(["']?)(?!data:)([^"')]+)\1\s*\)/g,L=/^:(empty|(first|last|only|nth(-last)?)-(child|of-type))$/,B=/:(:first-(?:line|letter))/g,C=/(^|})\s*([^\{]*?[\[:][^{]+)/g,G=/([ +~>])|(:[a-z-]+(?:\(.*?\)+)?)|(\[.*?\])/g,H=/(:not\()?:(hover|enabled|disabled|focus|checked|target|active|visited|first-line|first-letter)\)?/g,P=/[^\w-]/g,K=/^(INPUT|SELECT|TEXTAREA|BUTTON)$/,J=/^(checkbox|radio)$/,v=m>6?/[\$\^*]=(['"])\1/:null,E=/([(\[+~])\s+/g,F=/\s+([)\]+~])/g,Q=/\s+/g,x=/^\s*((?:[\S\s]*\S)?)\s*$/,i="",o=" ",h="$1";(function(a,c){function d(){try{p.doScroll("left")}catch(a){setTimeout(d,50);return}b("poll")}function b(d){if(!(d.type=="readystatechange"&&f.readyState!="complete")&&((d.type=="load"?a:f).detachEvent("on"+d.type,b,!1),!e&&(e=!0)))c.call(a,d.type||d)}var e=!1,g=!0;if(f.readyState=="complete")c.call(a,i);else{if(f.createEventObject&&p.doScroll){try{g=!a.frameElement}catch(h){}g&&d()}k(f,"readystatechange",b);k(a,"load",b)}})(j,function(){for(var a in z){var c,d,b=j;if(j[a]){for(c=z[a].replace("*",a).split(".");(d=c.shift())&&(b=b[d]););if(typeof b=="function"){t=b;U();break}}}})}}})(this); -------------------------------------------------------------------------------- /public_records_portal/static/js/track.js: -------------------------------------------------------------------------------- 1 | /* add a request form found at /new */ 2 | $(document).ready(function(){ 3 | /* validates track request form */ 4 | $("#submitTrack").validate({ 5 | rules: { 6 | request_id: { 7 | required: true, 8 | number: true 9 | }, 10 | highlight: function(element) { 11 | $(element).closest('.control-group').removeClass('success').addClass('error'); 12 | }, 13 | success: function(element) { 14 | element 15 | .closest('.control-group').removeClass('error').addClass('success'); 16 | } 17 | }, 18 | }); 19 | }); -------------------------------------------------------------------------------- /public_records_portal/static/json/README.md: -------------------------------------------------------------------------------- 1 | These JSON files are being used to hold: 2 | 3 | - Copy for web application 4 | - Action.json describes the actions a member of the public can take to submit a request, as well as the actions to be taken by a city employee. The text from this file is used for the website's copy. It tells users what will happen when they use a particular feature and who will be able to view the messages or documents uploaded. 5 | - Notcityrecords.json: When a member of the public types in a particular word or phrase pertaining to a record not possessed by the City of Oakland while submitting a request, a message pops up explaining to the user they need to contact another municipality. This file keeps track of all the phrases and messages. 6 | -Prr_help.json: This is the copy displayed on the "New Request" page. It includes tips for submitting a public records request, as well as three examples of public records requests. 7 | -Tutorial: The copy for the tutorial can be found here. 8 | 9 | 10 | - City Directories and contact information 11 | - Directory.json: The file contains the name and contact information for all City of Oakland employees. It is used to provide contact information to the public on the web page containing the request and responses from city employees. 12 | - Departments.json: This file contains the list of Public Records Request (PRR) Liaisons and their backup. (The PRR Liaisons are the ones responsible for fulfilling public records requests in the City of Oakland.) The liaisons are referenced using their email addresses. The file also contains a "Doc Types" field. These are the types of documents you can find within the department. 13 | - Do_not_email.json: If there is a city employee who should not receive email notifications, they are added to this list. 14 | 15 | Emails.json is a comprehensive list of they type of email notifications sent by this web application. It also includes information on who receives each notification and the email subject lines. 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public_records_portal/static/json/emails.json: -------------------------------------------------------------------------------- 1 | { 2 | "Recipient types": 3 | ["Requester", "Subscribers", "Subscriber", "Staff owner", "Departmental backups", "Staff participants", "Staff participant"], 4 | "Notification types": 5 | { 6 | "Request assigned": 7 | { 8 | "Subject": "A request has been assigned to you", 9 | "Recipients": ["Staff owner"], 10 | "Email body": "We're not using this but we could." 11 | }, 12 | "Staff participant added": 13 | { "Subject": "A request has been assigned to you", 14 | "Recipients": ["Staff participant"] 15 | }, 16 | "Question asked": 17 | { 18 | "Subject": "There is a question about your request.", 19 | "Recipients":["Requester"] 20 | }, 21 | "Question answered": 22 | { 23 | "Subject": "A question about this request has been answered by the requester.", 24 | "Recipients": ["Staff participants"] 25 | }, 26 | "Request closed": 27 | { 28 | "Subject": "The request has been closed", 29 | "Recipients": ["Staff participants", "Subscribers"] 30 | }, 31 | "City response added": 32 | { 33 | "Subject": "A response has been added to the request.", 34 | "Recipients": ["Subscribers"] 35 | }, 36 | "Request due": 37 | { 38 | "Subject": "A request in your department is due in 2 days or less.", 39 | "Recipients": ["Staff owner", "Departmental backups"] 40 | }, 41 | "Public note added": 42 | { 43 | "Subject": "A note has been added to this request by the requester.", 44 | "Recipients": ["Staff participants"] 45 | }, 46 | "Request made": 47 | { 48 | "Subject": "Your request has been submitted.", 49 | "Recipients": ["Requester"] 50 | }, 51 | "Request overdue": 52 | { 53 | "Subject": "A request in your department is overdue.", 54 | "Recipients": ["Staff owner", "Departmental backups"] 55 | }, 56 | "Request followed": 57 | { 58 | "Subject": "You are now subscribed to this request.", 59 | "Recipients": ["Subscriber"] 60 | } 61 | 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /public_records_portal/static/json/notcityrecords_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "Certificate": 3 | "The City of Oakland does not have copies of birth, death, or marriage certificates. Contact the County of Alameda's Clerk-Recorder's Office for these records by calling (510) 272-6362 or visiting acgov.org/auditor/clerk", 4 | "Divorce": 5 | "The City of Oakland does not have copies of divorce decrees or judgements. Contact the County of Alameda's Family Law Court for these records by calling (510) 263-4311 or visiting alameda.courts.ca.gov" 6 | } 7 | -------------------------------------------------------------------------------- /public_records_portal/static/json/tutorial.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome":"
Welcome to RecordTrac's tutorial. The tutorial will show you how to successfully manage a public records request with RecordTrac.
Important note: Every document or message you post through this application can be immediately viewed by the public.
", 3 | "Request Area":"

This section includes the submitted records request. Below the request is the requester's contact information.

Only city employees can view the requester’s name and phone number. Everyone can read the request.

", 4 | "Ask a Question":"

Click here to ask a question about this request.

The question will immediately be posted online and can be viewed by the public. The requester will be notified via email of your question.

You will get an email when the requester answers your question.

", 5 | "Add a Record":"
Add a record in one of three ways:
  • Upload a document stored on your computer by clicking on the “Select File” button.
  • Link to another website by adding a web address in the “Or provide a link to a record” field.
  • Provide instructions on how to view or obtain copies of records in the “Or indicate how they can be accessed offline” field.
You must provide a name for the document or link you post.
When you click the “Add Record” button, the document, link, or message will immediately become available to the public online. The requester will be notified by email when a record is added.
", 6 | "Add a Note":"

Use this section if there is a message you need to send to a requester and you cannot use any other feature.

This message will immediately become available to the public online. The requester will be notified by email a message has been posted.

", 7 | "Request Extension":"

Use this feature if you need additional time to respond to a request. You can select multiple reasons by holding down the “Shift” key and clicking on more than one reason. You then can edit the message provided to you.

When you click on the “Request an Extension” button, the message will immediately be posted online and anyone can read it. The requester will be notified by email of the extension.

", 8 | "Response Area":"

All of the messages you send to the requesters, links to websites you post, or documents you upload will be displayed here.

Everything posted here can be viewed by the public.

", 9 | "Reroute":"

Change the 'Point of Contact' of a request if your department does not have the information the requester needs. Click on 'Point of Contact.' Enter a city employee's name or email address and a short message explaining why you are reassigning the request.

Sometimes more than one person is needed to fulfill a request or it involves multiple departments. Click on 'Helpers' to notify additional city employees of this request.

", 10 | "Close request":"

Close a records request here. After selecting one or more reason, you have the opportunity to edit a message.

When you click on the “Close the Request” button, the message will immediately become available to the public online. An email will also be sent notifying the requester it was closed.

", 11 | "Feedback":"
Your feedback is how we improve RecordTrac. Click on the 'Feedback' tab to report a problem, suggest a new feature, or tell us what you like about RecordTrac.
", 12 | "Back to Tutorial":"

Congratulations! You've completed the tutorial.

You can come back to review the content any time by clicking on this tab.

" 13 | } -------------------------------------------------------------------------------- /public_records_portal/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block header %} 4 | 11 | {% endblock %} 12 | 13 | {% block container %} 14 |
15 | 18 |

Page Not Found

19 |

home

20 | 23 |
24 | {% endblock container%} -------------------------------------------------------------------------------- /public_records_portal/templates/_add_person.html: -------------------------------------------------------------------------------- 1 |
Notify: ×
2 |
3 |
4 | 5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /public_records_portal/templates/_ask_question.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 |
9 |
Ask a Question
10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /public_records_portal/templates/_contact_popover.html: -------------------------------------------------------------------------------- 1 | 2 | {% set staff = req.point_person() %} 3 | {% if staff %} 4 | {% set uid = staff.user.id %} 5 | {{ staff.user.alias or staff.user.email }} 6 | {% endif %} 7 | -------------------------------------------------------------------------------- /public_records_portal/templates/_history.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | History 4 |
5 |
Routing history
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for owner in req.owners %} 19 | {% set alias = "alias" | get_attribute(owner.user_id, "User") %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% if not owner.active %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% endif %} 34 | {% endfor %} 35 | 36 |
NameDateNote
{{ alias }}{{ owner.date_updated | date }}{{ owner.reason or "N/A" }}
{{ alias }}{{ owner.date_updated | date }}{{ owner.reason_unassigned or "Unassigned" }}
37 |
38 | -------------------------------------------------------------------------------- /public_records_portal/templates/_manage_requests_status.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | {% if status == 'closed' %}{% endif%} 5 | {{ status }} 6 |

7 |
8 | 9 | {% if current_user.is_authenticated() and status == 'closed' %} 10 |
11 |
12 | 13 | 14 |
15 |
16 | {% endif %} 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public_records_portal/templates/_public_note.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 | 8 |
9 |
10 |
Add a Note
11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 |
23 | 24 |
25 | 26 |
27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public_records_portal/templates/_reroute.html: -------------------------------------------------------------------------------- 1 |
Reassign to: ×
2 |
3 |
4 | 5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /public_records_portal/templates/_staff_participants.html: -------------------------------------------------------------------------------- 1 | {% for owner in req.owners %} 2 | {% if owner.active %} 3 | {% set participant = (owner | display_staff_participant(req)) %} 4 | {% if participant %} 5 | {% if current_user.is_authenticated() %} 6 |
7 |
8 | {{ participant }} remove 9 |
10 |
Remove helper: ×
11 |
12 |
13 | 14 |
15 |
16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 |
25 | {% else %} 26 |
27 | {{ participant }} 28 |
29 | {% endif %} 30 | {% endif %} 31 | {% endif %} 32 | {% endfor %} 33 | 34 | 35 | -------------------------------------------------------------------------------- /public_records_portal/templates/_timestamp.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ response.date() | date }} 4 |
5 |
6 | {{ response.date() | timestamp }} 7 |
8 |
9 | -------------------------------------------------------------------------------- /public_records_portal/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{title}}{% endblock title %} 3 | 4 | {% block container %} 5 | 6 |
7 |
8 | 9 |
10 |
11 |

About RecordTrac

12 |

RecordTrac is a quick, simple way for you to submit a public records request to {{config['AGENCY_NAME']}}.

13 |

Every message or record uploaded on this site is completely public. This makes easier to understand what happens to every request.

14 |
15 |
16 |

What is RecordTrac?

17 |

RecordTrac is a quick, simple way for individuals to submit public record requests to a governmental agency. The web application also allows government employees manage, respond to, and fulfill incoming requests. By displaying all records requests and responsive documents, members of the public may find what they need without having to create a new public record request.

18 |
19 |
20 |

Why aren't all documents uploaded on RecordTrac?

21 |

{{config['AGENCY_NAME']}} will not publicly release records or information that violates your right to privacy. This also includes records that will compromise your safety or the completion of a law enforcement investigation.

22 | 23 |

For example, many police reports contain sensitive information that can only be sent to the individuals directly involved in the crime or incident, and will not be released through RecordTrac. You will, however, better understand the number and types of records requests processed, even when the record itself cannot be uploaded due to sensitive data.

24 |
25 |
26 |

History of RecordTrac

27 | 28 |
29 |
30 | City of Oakland logo 31 | 32 | Code for America 33 |
34 |
35 | 36 |

The 2013 Code for America fellows created RecordTrac with the City of Oakland. As public records requests became larger and more complex, city employees found coordinating a response to be difficult. The old software system no longer met their needs and many requests remained untracked.

37 |

Members of the public complained they didn’t understand what happened after they submitted their requests and it took too long to get the records they needed.

38 |

By making everything completely transparent, you can know your request is fulfilled.

39 |
40 | 41 |
42 |

I'd like to know more about how to...

43 | 49 |

For more information, please see the full documentation for the RecordTrac application.

50 |
51 | 52 |
53 |
54 |
55 | {% endblock container %} 56 | 57 | 58 | -------------------------------------------------------------------------------- /public_records_portal/templates/actions.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Actions{% endblock %} 3 | {% block head %} {% endblock head %} 4 | {% block body %} 5 |

These are the actions you can take on this site. They may change.


6 | {% for action in actions %} 7 |

{{action}}

8 | {% endfor %} 9 | 10 | {% endblock body %} -------------------------------------------------------------------------------- /public_records_portal/templates/admin.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin/master.html' %} 2 | 3 | {% block main_menu %} 4 | {{ super() }} 5 | 11 | {% endblock %} 12 | 13 | {% block body %} 14 | 15 | 16 |

Administrative Controls

17 |

18 | RecordTrac’s administrative controls allows users to permanently delete or records, requests, and notes from the database. Because of this, administrative access is restricted to a small number of users. 19 |

20 | 21 |
Manage content
22 | 23 | 24 | 25 | 26 | 27 |
Best Practices to Consider
28 |

Content should only be removed or edited if sensitive or confidential information is revealed. If this happens, you should: 29 |

    30 |
  • Save a copy of the original message. (This will have to be done outside of RecordTrac. There is no way to hide a message from public view.)
  • 31 |
  • Edit the message to indicate why it needs to be removed.
  • 32 |
  • Notify the requester why their post or answer was removed.
  • 33 |
  • Provide guidance to the requester on how they can get the record they need.
  • 34 |
35 |

36 | 37 |

If a city staff member enters information incorrectly, simply add a note explaining the mistake.

38 | 39 |

If a member of the public enters incorrect information, the requester (or a city staff member) can add a note correcting the mistake.

40 | 41 |

Sometimes it’s necessary to create a new request. If a new request must be created, we suggest you do the following: 42 |

    43 |
  • Create a new request with the proper information.
  • 44 |
  • In the old request, include a note explaining what is wrong with it and a link to the new request.
  • 45 |
  • Close out the old request.
  • 46 |
  • In the new request, reference and/or provide a link to the old request.
  • 47 |
48 |

49 |

Although RecordTrac has a spam filter, every once in a while it may receive spam. When confronted with spam, close the request with a note indicating why it is not a public records request. If there is a large amount of spam requests, it is appropriate to simply remove the spam.

50 | 51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /public_records_portal/templates/alpha.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block container %} 4 |
5 |
6 | {% block banner %} 7 | {{ super() }} 8 | {% endblock banner %} 9 |
10 |

You have entered the public records request portal for the {{config['AGENCY_NAME']}}. This view requires authorized access.

11 | 12 |

If you are on {{config['AGENCY_NAME']}} staff, please login above to view and respond to the request.

13 | 14 |

If you are not a {{config['AGENCY_NAME']}} employee, you can still view requests here.

15 |
16 |
17 |
18 | {% endblock container %} 19 | -------------------------------------------------------------------------------- /public_records_portal/templates/closed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% extends "case.html" %} 5 | {% block title %}Request {{ req.id }}{% endblock %} 6 | 7 | {% block status %} 8 | {% set status = req.solid_status() %} 9 | {% include '_manage_requests_status.html' %} 10 | {% endblock status %} 11 | 12 | 13 | {% block follow %}{% endblock follow %} 14 | -------------------------------------------------------------------------------- /public_records_portal/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block container %} 3 |
4 | 5 | {% block error_generic %} 6 | Oops, something went wrong... 7 | {% endblock error_generic %} 8 | 9 | {% block error_specific %} 10 |

{{ message | safe }}

11 | {% endblock error_specific %} 12 | 13 |
14 | {% endblock container %} 15 | -------------------------------------------------------------------------------- /public_records_portal/templates/generic.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block container %} 3 |
4 |

5 |

{{ message }}

6 | 7 | {% endblock container %} 8 | -------------------------------------------------------------------------------- /public_records_portal/templates/generic_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 | 9 | 10 |

11 | You can view the request and take any necessary action at the following webpage: {{page}} 12 |

13 |
14 | 15 | {% if unfollow_link %} 16 |

17 | You can always choose to unfollow this request if you don't want to receive more emails about it. 18 |

19 | {% endif %} 20 |
21 | 22 |
23 |
24 |
25 |
26 | 27 |
28 | 29 | 30 |
31 |

32 | 33 | {{config['AGENCY_NAME']}} 34 | 35 |
36 | 37 |
38 | 39 | -------------------------------------------------------------------------------- /public_records_portal/templates/help_text.html: -------------------------------------------------------------------------------- 1 | {% macro help(name, type) -%} 2 |
3 |
4 | {{ type | explain_action }} 5 |
6 |
7 | {%- endmacro %} -------------------------------------------------------------------------------- /public_records_portal/templates/help_with_uploads.html: -------------------------------------------------------------------------------- 1 | {% extends "error.html" %} 2 | {% block error_specific %} 3 | {{ super() }} 4 |

5 | If you're having trouble uploading the record from our interface, or the file extension you are trying to upload is not supported*, we recommend uploading directly to the city's open data portal [instructions to come]. It is best to upload csv and other data files to the open data portal. After doing so, you can provide a link to the record instead of uploading it. 6 |

7 |

8 | If you are trying to upload emails from Outlook, we recommend saving it as a .txt file and uploading that. 9 |

10 | *Supported file types are:
11 | 'txt', 'pdf', 'doc', 'ps', 'rtf', 'epub', 'key', 'odt', 'odp', 'ods', 'odg', 'odf', 'sxw', 'sxc', 'sxi', 'sxd', 'ppt', 'pps', 'xls', 'zip', 'docx', 'pptx', 'ppsx', 'xlsx', 'tif', 'tiff' 12 |
13 | {% endblock error_specific %} 14 | -------------------------------------------------------------------------------- /public_records_portal/templates/landing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "base.html" %} 4 | {% block title %}RecordTrac{% endblock title %} 5 | 6 | {% block custom_css_links %} 7 | 8 | {% endblock custom_css_links %} 9 | 10 | {% block container %} 11 | 12 |
13 |
14 |
15 |
16 |

RecordTrac

17 |

A better way to find public records.

18 |
19 |
20 | {{config['AGENCY_NAME']}} logo 21 |
22 |
23 |
24 |

Use RecordTrac to tell the {{config['AGENCY_NAME']}} the type of documents you need. We display every message or record uploaded. You may find what you're looking for without having to submit a new request. Find out more…

25 |
26 | 63 | 64 | 69 |
70 |
71 | 72 | 73 | 99 | 100 | 101 | {% endblock container %} 102 | 103 | {% block footer %} 104 | 108 | {% endblock footer %} 109 | 110 | {% block custom_script_links %} 111 | 112 | 113 | {% endblock custom_script_links %} -------------------------------------------------------------------------------- /public_records_portal/templates/manage_request_feedback.html: -------------------------------------------------------------------------------- 1 | {% extends "closed.html" %} 2 | {% block status %} 3 | {{ super() }} 4 | 5 | {% endblock status %} 6 | -------------------------------------------------------------------------------- /public_records_portal/templates/manage_request_follower.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "case.html" %} 4 | {% block title %}Request {{ request_id }}{% endblock %} 5 | {% block head %} 6 | {{ super() }} 7 | {% endblock %} 8 | 9 | {% block banner %} 10 | 11 |
12 | 13 |
14 |
15 |

You are now subscribed! You will be contacted via e-mail at with any updates to this request. 16 |

17 |
18 | {% endblock banner %} 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public_records_portal/templates/manage_request_new.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "case.html" %} 4 | {% block title %}Request {{ request_id }}{% endblock %} 5 | {% block head %} 6 | {{ super() }} 7 | {% endblock %} 8 | 9 | {% block banner %} 10 | {% set requester = req.requester() %} 11 | {% if (requester) %} 12 | {% set email = requester.user.email %} 13 | {% endif %} 14 | {% if email %} 15 |
16 | 17 |
18 | {% else %} 19 |
20 | 21 |
22 | {% endif %} 23 |
24 | {% if current_user.is_authenticated() %} 25 |

The request has been submitted!

26 |

27 | {% if email %} 28 | The requester has been notified via email that they can expect to hear a response from the {{config['AGENCY_NAME']}} in the next 10 days. Requester will be automatically contacted with any updates. 29 | {% else %} 30 | Because the requester did not provide an email address, the agency is responsible for communicating updates to the requester in the format received. 31 | {% endif %} 32 |

33 | {% else %} 34 |

Your request has been submitted!

35 |

You can expect a response from the {{config['AGENCY_NAME']}} in the next 10 days. 36 | {% if email %} 37 | You will be contacted via e-mail with any updates. 38 | {% else %} 39 | You did not provide an e-mail address so you will have to visit this page for updates. 40 | {% endif %} 41 |

42 |

All messages from the {{config['AGENCY_NAME']}} and/or the information and documents you requested will be posted to this page. You can access this page at any time.

43 | {% endif %} 44 |
45 |
46 | {% endblock banner %} 47 | 48 | 49 | -------------------------------------------------------------------------------- /public_records_portal/templates/manage_request_public.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "case.html" %} 4 | 5 | {% block public_note %} 6 | {% include '_public_note.html' %} 7 | {% endblock public_note %} 8 | 9 | {% block status %} 10 | {% set status = req.solid_status() %} 11 | {% include '_manage_requests_status.html' %} 12 | {% endblock status %} 13 | 14 | 15 | -------------------------------------------------------------------------------- /public_records_portal/templates/manage_request_unfollow.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "case.html" %} 4 | {% block title %}Request {{ request_id }}{% endblock %} 5 | {% block head %} 6 | {{ super() }} 7 | {% endblock %} 8 | 9 | {% block banner %} 10 | 11 |
12 | 13 |
14 |
15 |

You have successfully unsubscribed from this request.

16 |

You will not receive any new email updates for this request.

17 |

If you would like to start receiving updates again, enter your email address and click the Follow button on the right side of this request page.

18 |
19 |
20 | {% endblock banner %} 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public_records_portal/templates/missing_email.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block container %} 3 |
4 |
5 |
6 |

Oops, looks like the email address is missing.

7 |

Enter an email if you have one. We need it to let you know when your record is ready.

8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | {% endblock container %} 25 | 26 | {% block custom_script_links %} 27 | 28 | {% endblock custom_script_links %} -------------------------------------------------------------------------------- /public_records_portal/templates/new_request_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 | 9 |

10 | Thank you for submitting a public records request to the {{config['AGENCY_NAME']}}. You will receive email updates about your request, but you can always view any activity on the request page, {{page}}. 11 |

12 |

In the next {{config['DAYS_TO_FULFILL']}} days, you can expect a response from {{config['AGENCY_NAME']}}.
13 | A response can be the following:

    14 |
  • a question about your request
  • 15 |
  • an estimate on when your records will be delivered
  • 16 |
  • a note explaining why the records cannot be released
  • 17 |
  • the documents you requested

18 |

This email is an automated notification, which is unable to receive replies. Please contact {{config['DEFAULT_OWNER_EMAIL']}} with any questions or concerns.

19 |
20 |

21 |


22 |
23 |
24 | 25 |
26 | For more information about RecordTrac, visit http://recordtrac.com. 27 |
28 |
29 |

30 | 31 |
32 |
33 |
34 |
35 | 36 |
37 | 38 | 39 |
40 |

41 | {{config['AGENCY_NAME']}} 42 | 43 |
44 | 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /public_records_portal/templates/password_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 | 9 | 10 |

11 | Your password has been reset. To log in, use your agency's e-mail address and the temporary password below: {{page}} 12 |
13 | Temporary password:
14 | {{ password }}

15 |

After logging in with the temporary password, please update your password. 16 |

17 |
18 |

19 |


20 |
21 |
22 | 23 | 24 |

25 | 26 |
27 |
28 |
29 |
30 | 31 |
32 | 33 | 34 | 35 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /public_records_portal/templates/recaptcha_answer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "base.html" %} 4 | {% block title %}Captcha{% endblock title %} 5 | 6 | {% block container %} 7 | 8 | 9 |
10 |
11 |
12 | {{ message }} 13 |
14 |
15 |
16 |
17 | 20 | 28 | {% if form %} 29 | 30 | 31 | 32 | {% endif %} 33 | 34 | 35 | 36 |
37 |
38 |
39 |
40 |
41 | {% endblock container %} 42 | 43 | -------------------------------------------------------------------------------- /public_records_portal/templates/recaptcha_note.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "base.html" %} 4 | {% block title %}Captcha{% endblock title %} 5 | 6 | {% block container %} 7 | 8 | 9 |
10 |
11 |
12 | {{ message }} 13 |
14 |
15 |
16 |
17 | 20 | 28 | {% if form %} 29 | 30 | 31 | {% endif %} 32 | 33 | 34 | 35 |
36 |
37 |
38 |
39 |
40 | {% endblock container %} 41 | 42 | -------------------------------------------------------------------------------- /public_records_portal/templates/recaptcha_request.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "base.html" %} 4 | {% block title %}Captcha{% endblock title %} 5 | 6 | {% block container %} 7 | 8 | 9 |
10 |
11 |
12 | {{ message }} 13 |
14 |
15 |
16 |
17 | 20 | 28 | {% if form %} 29 | 30 | 31 | 32 | 33 | 34 | {% endif %} 35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 |
43 | {% endblock container %} 44 | 45 | -------------------------------------------------------------------------------- /public_records_portal/templates/reset_password.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block container %} 3 | 4 |
5 |
6 |
7 | {% if after_reset %} 8 | {% if reset_success %} 9 |

Your password has been successfully reset.

10 |

Please check your email for a temporary password.

11 | {% else %} 12 |

There was an issue resetting your password.

13 |

Most likely, you are not a user in the system.

14 | {% endif %} 15 | {% else %} 16 |

Reset your password

17 |

Type your email address in the field below. 18 |

Once you click on the "Reset" button, an email will be sent to you. The email will contain a temporary password that you can use to log into RecordTrac.

19 |
20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 |
36 | {% endif %} 37 | {% block contact %} 38 |
39 |
40 |

41 | If a password reset still doesn't help, please reach out to our team. 42 |

43 | {% endblock contact %} 44 |
45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | {% endblock container %} 54 | 55 | -------------------------------------------------------------------------------- /public_records_portal/templates/response.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "base.html" %} 4 | {% block title %}Request{% endblock %} 5 | {% block content %} 6 | {% block well %} 7 | {% block request %} 8 | 9 |
10 |

Request #{{req.id}}

11 |
12 |

{{ req.text }}

13 |
14 |
15 | {% endblock request %} 16 | {% block response %} 17 | 18 |
19 |

Response

20 | 21 | {% set responses = ( req | get_responses_chronologically ) %} 22 | {% if responses %} 23 |
24 | {% for response in responses %} 25 |
26 |
27 |
28 | {{ response.date | date_granular }}
29 |
30 |
31 |

32 | {{ response.text | safe }} - {{ response.staff_alias or response.staff_email }}. 33 |

34 |
35 |
36 | {% endfor %} 37 |
38 | {% else %} 39 |
40 |


No records uploaded yet.

41 |
42 | {% endif %} 43 | {% endblock response %} 44 | 45 |
46 | 47 | {% endblock well %} 48 | {% endblock content %} -------------------------------------------------------------------------------- /public_records_portal/templates/signup.html: -------------------------------------------------------------------------------- 1 | {# 2 | This template is for an interest signup. 3 | #} 4 | 5 | 6 | 7 | 8 | 9 | Signup 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 | 28 |

29 | RecordTrac 30 |

31 | 32 |

33 | RecordTrac is a new web application that helps agencies manage and respond to public records requests. It also gives the public a quick, easy way to submit public records requests. 34 |

35 |

36 | Anyone can view every uploaded document and message submitted through this application. This allows the public and agency employees to search through past requests, view previously released records, and better understand what happens to each request. 37 |

38 |

39 | If you are interested in using RecordTrac in your agency, we'd love to connect. Just fill out the form to the right. 40 |

41 | 42 | More info... 43 | 44 |
45 | Code for America 46 |
47 |
48 | 49 |
50 | 51 | 52 |
53 |
54 |
55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /public_records_portal/templates/staff_card.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block container %} 4 | 5 | {% set staff = ("User"| get_obj(uid)) %} 6 |
7 |
8 | {% if staff.email %} 9 | {% set gravatar_url = staff.email | get_gravatar_url %} 10 |
11 |
12 |
13 |

{{staff.alias}}

14 |
Department: {{staff.department_name()}}
15 | 16 |
Phone: {{staff.phone or "N/A"}}
17 | {% else %} 18 |
Staff ID provided does not exist.
19 | {% endif %} 20 |
21 |
22 | 23 | {% endblock container %} 24 | -------------------------------------------------------------------------------- /public_records_portal/templates/track.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "base.html" %} 4 | {% block title %}Track{% endblock title %} 5 | 6 | {% block custom_css_links %} 7 | 8 | {% endblock custom_css_links %} 9 | 10 | {% block container %} 11 |
12 | 13 |
14 |
15 |
16 |

Track a request

17 |

Use the tracking tool to find a specific request. If you would prefer to browse all existing requests, you can explore here.

18 | 19 |
20 |
21 | 22 |

Enter your tracking number

23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 |
46 | 47 | {% endblock container %} 48 | 49 | {% block custom_script_links %} 50 | 51 | {% endblock custom_script_links %} -------------------------------------------------------------------------------- /public_records_portal/templates/uploaded.html: -------------------------------------------------------------------------------- 1 | {% extends "case.html" %} 2 | {% block title %} Index {% endblock title%} 3 | {% block head %} 4 | {{ super() }} 5 | {% endblock head %} 6 | 7 | {% block banner %} 8 | {{ super() }} 9 | 10 |
11 | 12 | 13 | {{record_uploaded.filename or record_uploaded.description}} has been successfully uploaded to this request. 14 |
15 | {% endblock banner %} 16 | 17 | -------------------------------------------------------------------------------- /public_records_portal/timeout.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import errno 3 | import os 4 | import signal 5 | 6 | 7 | def timeout(seconds=10, error_message=os.strerror(errno.ETIME)): 8 | def decorator(func): 9 | def _handle_timeout(signum, frame): 10 | raise Exception("Too long") 11 | 12 | def wrapper(*args, **kwargs): 13 | signal.signal(signal.SIGALRM, _handle_timeout) 14 | signal.alarm(seconds) 15 | try: 16 | result = func(*args, **kwargs) 17 | finally: 18 | signal.alarm(0) 19 | return result 20 | 21 | return wraps(func)(wrapper) 22 | 23 | return decorator -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | Flask-SQLAlchemy==0.16 3 | Flask-Restless==0.12.1 4 | Flask-Login==0.2.11 5 | Flask-WTF==0.8.3 6 | Jinja2==2.6 7 | SQLAlchemy==0.9.3 8 | Werkzeug==0.8.3 9 | psycopg2==2.4.6 10 | sendgrid==0.1.3 11 | gunicorn==0.17.4 12 | requests==1.1.0 13 | alembic==0.5.0 14 | Flask-Admin==1.0.9 15 | Flask-Cache==0.12 16 | recaptcha-client==1.0.6 17 | pytz==2012d 18 | anyjson 19 | newrelic 20 | git+https://github.com/garbados/flask-browserid.git 21 | validate-email==1.2 22 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from public_records_portal.prflask import app 2 | app.run(debug=True, port=5000) -------------------------------------------------------------------------------- /scripts/run-server.sh: -------------------------------------------------------------------------------- 1 | cd .. 2 | foreman start -------------------------------------------------------------------------------- /send_notifications.py: -------------------------------------------------------------------------------- 1 | from public_records_portal.notifications import notify_due 2 | 3 | # Notify city staff via e-mail if they belong to a request that is due soon or overdue: 4 | notify_due() 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /testing.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgresql://localhost/recordtractest 2 | TESTING=true --------------------------------------------------------------------------------