diff --git a/alembic/env.py b/alembic/env.py index 230bc0d3..6c7b8a2b 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -14,7 +14,7 @@ # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config -config.set_main_option("sqlalchemy.url", app.config['DATABASE_URL']) +config.set_main_option("sqlalchemy.url", app.config['SQLALCHEMY_DATABASE_URI']) # Interpret the config file for Python logging. # This line sets up loggers basically. diff --git a/alembic/versions/10855dae57b7_add_foreign_keys_bet.py b/alembic/versions/10855dae57b7_add_foreign_keys_bet.py new file mode 100644 index 00000000..2e466140 --- /dev/null +++ b/alembic/versions/10855dae57b7_add_foreign_keys_bet.py @@ -0,0 +1,26 @@ +"""add foreign keys between User and Department + +Revision ID: 10855dae57b7 +Revises: 134e5fea7601 +Create Date: 2015-05-19 10:59:16.315012 + +""" + +# revision identifiers, used by Alembic. +revision = '10855dae57b7' +down_revision = '134e5fea7601' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.alter_column('user', 'department', new_column_name='department_id') + op.add_column('department', sa.Column('primary_contact_id', sa.INTEGER, sa.ForeignKey("user.id"))) + op.add_column('department', sa.Column('backup_contact_id', sa.INTEGER, sa.ForeignKey("user.id"))) + + +def downgrade(): + op.drop_column('department', 'primary_contact_id') + op.drop_column('department', 'backup_contact_id') + op.alter_column('user', 'department_id', new_column_name='department') diff --git a/alembic/versions/2de765e466ca_convert_created_date.py b/alembic/versions/2de765e466ca_convert_created_date.py new file mode 100644 index 00000000..22c218dc --- /dev/null +++ b/alembic/versions/2de765e466ca_convert_created_date.py @@ -0,0 +1,51 @@ +"""convert created datetimes to utc + +Revision ID: 2de765e466ca +Revises: 10855dae57b7 +Create Date: 2015-05-19 23:01:13.266389 + +""" + +# revision identifiers, used by Alembic. +revision = '2de765e466ca' +down_revision = '10855dae57b7' + +from alembic import op +import sqlalchemy as sa +from datetime import datetime +from pytz import timezone, utc + +pacific = timezone('US/Pacific') +Request = sa.Table( + 'request', + sa.MetaData(), + sa.Column('id', sa.Integer, primary_key=True), + sa.Column('date_created', sa.DateTime), +) + +def upgrade(): + connection = op.get_bind() + + # Assume all existing requests were entered in Pacific time, and + # convert them to UTC. + for request in connection.execute(Request.select()): + date_created = request.date_created.replace(tzinfo=pacific) + date_created = date_created.astimezone(utc) + connection.execute( + Request.update().where(Request.c.id == request.id) + .values(date_created=date_created) + ) + + +def downgrade(): + connection = op.get_bind() + + # Assume all existing requests were entered in UTC, and convert + # them to Pacific time. + for request in connection.execute(Request.select()): + date_created = request.date_created.replace(tzinfo=utc) + date_created = date_created.astimezone(pacific) + connection.execute( + Request.update().where(Request.c.id == request.id) + .values(date_created=date_created) + ) diff --git a/public_records_portal/RequestPresenter.py b/public_records_portal/RequestPresenter.py index 7a016aa8..5248e5bd 100644 --- a/public_records_portal/RequestPresenter.py +++ b/public_records_portal/RequestPresenter.py @@ -28,8 +28,8 @@ def __init__(self, request, qa = None, note = None, index = None, public = False if self.staff: if self.staff.email: self.staff_email = self.staff.email - if self.staff.department: - self.staff_department = self.staff.department + if self.staff.department_id: + self.staff_department = self.staff.department_id if self.staff.phone: self.staff_phone = self.staff.phone if self.staff.alias: @@ -41,7 +41,7 @@ def __init__(self, request, qa = None, note = None, index = None, public = False self.response = note self.type = "note" self.icon = "icon-edit icon-large" - + def get_id(self): return self.response.id @@ -60,14 +60,14 @@ def display_text(self): - + """ % (self.response.id, self.request.id) else: text = text + "

Requester hasn't answered yet.

" return text elif self.type == "note": return "%s - Requester" %(self.response.text) - + def get_icon(self): return self.icon diff --git a/public_records_portal/__init__.py b/public_records_portal/__init__.py index 4eaddd55..5d82a5e7 100644 --- a/public_records_portal/__init__.py +++ b/public_records_portal/__init__.py @@ -11,7 +11,7 @@ from flask import Flask from flask.ext.sqlalchemy import SQLAlchemy -# Initialize Flask app +# Initialize Flask app app = Flask(__name__) app.debug = True @@ -24,9 +24,15 @@ def set_env(key, default = None): elif default: app.config[key] = default +def set_bool_env(key, default = None): + if key in environ: + app.config[key] = environ[key].lower() in ('true', 'yes', 'on') + elif default is not None: + app.config[key] = default + # UPDATES TO THESE DEFAULTS SHOULD OCCUR IN YOUR .env FILE. -set_env(key = 'APPLICATION_URL', default = "http://127.0.0.1:5000/") +set_env(key = 'APPLICATION_URL', default = "http://127.0.0.1:5000/") set_env(key = 'ENVIRONMENT', default="LOCAL") # The default records liaison, to whom requests get routed to if no department is selected: set_env(key = 'DEFAULT_OWNER_EMAIL', default = 'recordtrac@postcode.io') @@ -40,7 +46,7 @@ def set_env(key, default = None): # Currently due dates and overdue status is only showed to logged in agency staff set_env(key = 'DAYS_TO_FULFILL', default = '10') set_env(key = 'DAYS_AFTER_EXTENSION', default = '14') -set_env(key = 'DAYS_UNTIL_OVERDUE', default = '2') +set_env(key = 'DAYS_UNTIL_OVERDUE', default = '2') set_env(key = 'TIMEZONE', default = "US/Pacific") @@ -71,7 +77,7 @@ def set_env(key, default = None): set_env(key = envvar) # Database gets set slightly differently, to support difference between Flask and Heroku naming: -app.config['SQLALCHEMY_DATABASE_URI'] = environ['DATABASE_URL'] +app.config['SQLALCHEMY_DATABASE_URI'] = environ['DATABASE_URL'] # Initialize database db = SQLAlchemy(app) diff --git a/public_records_portal/db_helpers.py b/public_records_portal/db_helpers.py index 123a4904..e332ca90 100644 --- a/public_records_portal/db_helpers.py +++ b/public_records_portal/db_helpers.py @@ -1,4 +1,4 @@ -""" +""" .. module:: db_helpers :synopsis: Functions that interact with the Postgres database via Flask-SQLAlchemy .. modlueauthor:: Richa Agarwal @@ -14,7 +14,7 @@ import uuid import json import os -import logging +import logging ### @export "get_subscriber" @@ -264,9 +264,9 @@ def update_user(user, alias = None, phone = None, department = None, contact_for if type(department) != int and not department.isdigit(): d = Department.query.filter_by(name = department).first() if d: - user.department = d.id + user.department_id = d.id else: - user.department = department + user.department_id = department if contact_for: if user.contact_for and contact_for not in user.contact_for: contact_for = user.contact_for + "," + contact_for @@ -328,7 +328,7 @@ def add_staff_participant(request_id, is_point_person = False, email = None, use participant.is_point_person = True participant.date_updated = datetime.now().isoformat() if reason: # Update the reason - participant.reason = reason + participant.reason = reason app.logger.info("\n\nStaff participant with owner ID: %s is now the point of contact for request %s" %(participant.id, request_id)) else: is_new = False diff --git a/public_records_portal/filters.py b/public_records_portal/filters.py index 51499b86..66999f24 100644 --- a/public_records_portal/filters.py +++ b/public_records_portal/filters.py @@ -20,6 +20,7 @@ app.jinja_env.filters['get_request_data_chronologically'] = prr.get_request_data_chronologically app.jinja_env.filters['get_gravatar_url'] = gravatar.get_gravatar_url app.jinja_env.filters['date'] = helpers.date +app.jinja_env.filters['format_datetime'] = helpers.format_datetime app.jinja_env.filters['explain_action'] = helpers.explain_action app.jinja_env.filters['tutorial'] = helpers.tutorial app.jinja_env.filters['new_lines'] = helpers.new_lines diff --git a/public_records_portal/helpers.py b/public_records_portal/helpers.py index 8fbf15fe..d5642341 100644 --- a/public_records_portal/helpers.py +++ b/public_records_portal/helpers.py @@ -56,6 +56,16 @@ def date(obj): except: # Not a datetime object return notifications.format_date(datetime.strptime(obj, "%Y-%m-%dT%H:%M:%S.%f")) +def format_datetime(obj, format): + """ Take a datetime or datetime-like object and return a formatted datetime string. """ + if not obj: + return None + try: + return localize(obj).strftime(format) + except: # Not a datetime object + date_obj = datetime.strptime(obj, "%Y-%m-%dT%H:%M:%S.%f") + return localize(date_obj).strftime(format) + def timestamp(obj): return localize(obj).strftime('%H:%M:%S') diff --git a/public_records_portal/models.py b/public_records_portal/models.py index 3bca0deb..e24c170d 100644 --- a/public_records_portal/models.py +++ b/public_records_portal/models.py @@ -15,6 +15,7 @@ from sqlalchemy import and_, or_ from datetime import datetime, timedelta +from pytz import utc from public_records_portal import db, app from werkzeug.security import generate_password_hash, check_password_hash import json @@ -31,14 +32,17 @@ class User(db.Model): phone = db.Column(db.String()) date_created = db.Column(db.DateTime) password = db.Column(db.String(255)) - department = db.Column(Integer, ForeignKey("department.id")) - current_department = relationship("Department", foreign_keys = [department], uselist = False) + department_id = db.Column(Integer, ForeignKey("department.id", use_alter=True, name="fk_department")) contact_for = db.Column(db.String()) # comma separated list backup_for = db.Column(db.String()) # comma separated list owners = relationship("Owner") subscribers = relationship("Subscriber") is_staff = db.Column(db.Boolean, default = False) # Is this user an active agency member? + current_department = relationship("Department", + foreign_keys=[department_id], + lazy='joined', uselist=False) + def is_authenticated(self): return True def is_active(self): @@ -63,7 +67,7 @@ def __init__(self, email=None, alias = None, phone=None, department = None, cont self.phone = phone self.date_created = datetime.now().isoformat() if department and department != "": - self.department = department + self.department_id = department if contact_for and contact_for != "": self.contact_for = contact_for if backup_for and backup_for != "": @@ -88,9 +92,21 @@ class Department(db.Model): date_created = db.Column(db.DateTime) date_updated = db.Column(db.DateTime) name = db.Column(db.String(), unique=True) - users = relationship("User") # The list of users in this department + users = relationship("User", foreign_keys=[User.department_id], post_update=True) # The list of users in this department requests = relationship("Request", order_by = "Request.date_created.asc()") # The list of requests currently associated with this department - def __init__(self, name): + + primary_contact_id = db.Column(Integer, ForeignKey("user.id")) + backup_contact_id = db.Column(Integer, ForeignKey("user.id")) + primary_contact = relationship(User, + foreign_keys=[primary_contact_id], + primaryjoin=(primary_contact_id == User.id), + uselist=False, post_update=True) + backup_contact = relationship(User, + foreign_keys=[backup_contact_id], + primaryjoin=(backup_contact_id == User.id), + uselist=False, post_update=True) + + def __init__(self, name=''): self.name = name self.date_created = datetime.now().isoformat() def __repr__(self): @@ -101,7 +117,7 @@ def get_name(self): return self.name or "N/A" ### @export "Request" -class Request(db.Model): +class Request(db.Model): # The public records request __tablename__ = 'request' id = db.Column(db.Integer, primary_key =True) @@ -124,7 +140,7 @@ class Request(db.Model): def __init__(self, text, creator_id = None, offline_submission_type = None, date_received = None): self.text = text - self.date_created = datetime.now().isoformat() + self.date_created = datetime.now(utc).isoformat() self.creator_id = creator_id self.offline_submission_type = offline_submission_type if date_received and type(date_received) is datetime: @@ -142,7 +158,7 @@ def set_due_date(self): self.due_date = self.date_received + timedelta(days = int(app.config['DAYS_TO_FULFILL'])) def extension(self): - self.extended = True + self.extended = True self.due_date = self.due_date + timedelta(days = int(app.config['DAYS_AFTER_EXTENSION'])) def point_person(self): for o in self.owners: @@ -154,7 +170,7 @@ def all_owners(self): for o in self.owners: all_owners.append(o.user.get_alias()) return all_owners - + def requester(self): if self.subscribers: return self.subscribers[0] or None # The first subscriber is always the requester @@ -207,11 +223,11 @@ def open(self): def due_soon(self): two_days = datetime.now() + timedelta(days = 2) return and_(self.due_date < two_days, self.due_date > datetime.now(), ~self.closed) - + @hybrid_property def overdue(self): return and_(self.due_date < datetime.now(), ~self.closed) - + @hybrid_property def closed(self): return Request.status.ilike("%closed%") @@ -236,12 +252,12 @@ def __repr__(self): return "" %(self.question, self.answer) ### @export "Owner" -class Owner(db.Model): +class Owner(db.Model): # A member of city staff assigned to a particular request, that may or may not upload records towards that request. __tablename__ = 'owner' id = db.Column(db.Integer, primary_key =True) user_id = Column(Integer, ForeignKey('user.id')) - user = relationship("User", uselist = False) + user = relationship("User", uselist = False, lazy='joined') request_id = db.Column(db.Integer, db.ForeignKey('request.id')) request = relationship("Request", foreign_keys = [request_id]) active = db.Column(db.Boolean, default = True) # Indicate whether they're still involved in the request or not. @@ -259,15 +275,17 @@ def __init__(self, request_id, user_id, reason= None, is_point_person = False): self.is_point_person = is_point_person def __repr__(self): return '' %self.id + def __str__(self): + return str(self.user) ### @export "Subscriber" -class Subscriber(db.Model): +class Subscriber(db.Model): # A person subscribed to a request, who may or may not have created the request, and may or may not own a part of the request. __tablename__ = 'subscriber' id = db.Column(db.Integer, primary_key = True) should_notify = db.Column(db.Boolean, default = True) # Allows a subscriber to unsubscribe user_id = db.Column(db.Integer, db.ForeignKey('user.id')) - user = relationship("User", uselist = False) + user = relationship("User", uselist = False, lazy='joined') request_id = db.Column(db.Integer, db.ForeignKey('request.id')) date_created = db.Column(db.DateTime) owner_id = db.Column(db.Integer, db.ForeignKey('owner.id')) # Not null if responsible for fulfilling a part of the request. UPDATE 6-11-2014: This isn't used. we should get rid of it. @@ -277,6 +295,8 @@ def __init__(self, request_id, user_id, creator = False): self.date_created = datetime.now().isoformat() def __repr__(self): return '' %self.user_id + def __str__(self): + return str(self.user) ### @export "Record" class Record(db.Model): @@ -287,7 +307,7 @@ class Record(db.Model): user_id = db.Column(db.Integer, db.ForeignKey('user.id')) # The user who uploaded the record, right now only city staff can doc_id = db.Column(db.Integer) # The document ID. Currently using Scribd API to upload documents. request_id = db.Column(db.Integer, db.ForeignKey('request.id')) # The request this record was uploaded for - description = db.Column(db.String(400)) # A short description of what the record is. + description = db.Column(db.String(400)) # A short description of what the record is. filename = db.Column(db.String(400)) # The original name of the file being uploaded. url = db.Column(db.String()) # Where it exists on the internet. download_url = db.Column(db.String()) # Where it can be downloaded on the internet. @@ -332,6 +352,6 @@ class Visualization(db.Model): def __init__(self, type_viz, content): self.type_viz = type_viz self.content = content - self.date_created = datetime.now().isoformat() + self.date_created = datetime.now(utc).isoformat() def __repr__(self): return '' % self.type_viz diff --git a/public_records_portal/prflask.py b/public_records_portal/prflask.py index c942c025..046f5f75 100644 --- a/public_records_portal/prflask.py +++ b/public_records_portal/prflask.py @@ -8,11 +8,14 @@ +from datetime import date from public_records_portal import app, models, db, views from views import * # Import all the functions that render templates from flask.ext.restless import APIManager from flask.ext.admin import Admin, expose, BaseView, AdminIndexView -from flask.ext.admin.contrib.sqlamodel import ModelView +from flask.ext.admin.contrib.sqla import ModelView +from jinja2.filters import do_mark_safe +from wtforms.validators import ValidationError # Create API @@ -33,52 +36,81 @@ class HomeView(AdminIndexView): def home(self): return self.render('admin.html') def is_accessible(self): - if current_user.is_authenticated(): - if 'LIST_OF_ADMINS' in app.config: - admins = app.config['LIST_OF_ADMINS'].split(",") - if current_user.email.lower() in admins: - return True - return False + if current_user.is_authenticated(): + if 'LIST_OF_ADMINS' in app.config: + admins = app.config['LIST_OF_ADMINS'].split(",") + if current_user.email.lower() in admins: + return True + return False # Create Admin admin = Admin(app, name='RecordTrac Admin', url='/admin', index_view = HomeView(name='Home')) class AdminView(ModelView): def is_accessible(self): - if current_user.is_authenticated(): - if 'LIST_OF_ADMINS' in app.config: - admins = app.config['LIST_OF_ADMINS'].split(",") - if current_user.email.lower() in admins: - return True + if current_user.is_authenticated(): + if 'LIST_OF_ADMINS' in app.config: + admins = app.config['LIST_OF_ADMINS'].split(",") + if current_user.email.lower() in admins: + return True return False +def postdate_check(form, field): + if field.data.date() > date.today(): + raise ValidationError('This field cannot be post-dated') + class RequestView(AdminView): - can_create = False - can_edit = True - column_list = ('id', 'text', 'date_created', 'status') # The fields the admin can view - column_searchable_list = ('status', 'text') # The fields the admin can search a request by - form_excluded_columns = ('date_created', 'extended', 'status', 'status_updated', 'current_owner') # The fields the admin cannot edit. + can_create = False + can_edit = True + column_list = ('id', 'text', 'date_created', 'status') # The fields the admin can view + column_labels = dict(date_created='Date Created (UTC)') + column_searchable_list = ('status', 'text') # The fields the admin can search a request by + form_excluded_columns = ('date_created', 'extended', 'status', 'status_updated', 'current_owner') # The fields the admin cannot edit. + form_args = dict(date_received={ + 'validators': [postdate_check] + }) class RecordView(AdminView): - can_create = False - column_searchable_list = ('description', 'filename', 'url', 'download_url', 'access') - column_list = ('request_id', 'description', 'filename', 'url', 'download_url', 'access') - can_edit = False + can_create = False + column_searchable_list = ('description', 'filename', 'url', 'download_url', 'access') + column_list = ('request_id', 'description', 'filename', 'url', 'download_url', 'access') + can_edit = False class QAView(AdminView): - can_create = False - can_edit = True - column_list = ('request_id', 'question', 'answer', 'date_created') - form_excluded_columns = ('date_created') + can_create = False + can_edit = True + column_list = ('request_id', 'question', 'answer', 'date_created') + form_excluded_columns = ('date_created') class NoteView(AdminView): - can_create = False - can_edit = True - column_list = ('request_id', 'text', 'date_created') - form_excluded_columns = ('date_created') - + can_create = False + can_edit = True + column_list = ('request_id', 'text', 'date_created') + form_excluded_columns = ('date_created') + +class UserView(AdminView): + can_create = True + can_edit = True + column_list = ('alias', 'email', 'current_department', 'phone', 'is_staff') + column_labels = dict(alias='Name', current_department='Department', phone='Phone #') + column_descriptions = dict(is_staff='Determines whether the user can log in and edit data through this interface.') + form_excluded_columns = ('date_created', 'password', 'contact_for', 'backup_for') + +class DepartmentView(AdminView): + can_create = True + can_edit = True + column_list = ('name', 'primary_contact', 'backup_contact') + column_descriptions = dict(backup_contact='Note that if you want to assign a user that does not yet exist as the primary or backup contact for this department, you must create the user first.') + + form_columns = column_list + form_excluded_columns = ('date_created', 'date_updated') + form_args = dict(backup_contact={ + 'description': do_mark_safe(column_descriptions['backup_contact']) + }) admin.add_view(RequestView(models.Request, db.session)) admin.add_view(RecordView(models.Record, db.session)) admin.add_view(NoteView(models.Note, db.session)) admin.add_view(QAView(models.QA, db.session)) +admin.add_view(UserView(models.User, db.session)) +admin.add_view(DepartmentView(models.Department, db.session)) diff --git a/public_records_portal/prr.py b/public_records_portal/prr.py index 40b3d33f..d5c3d80f 100644 --- a/public_records_portal/prr.py +++ b/public_records_portal/prr.py @@ -26,6 +26,8 @@ def add_resource(resource, request_body, current_user_id = None): fields = request_body if "extension" in resource: + if not fields.getlist('extend_reason') and not fields.getlist('extend_reasons'): + return "You must select a reason for the extension." return request_extension(int(fields['request_id']), fields.getlist('extend_reason'), current_user_id) if "note" in resource: return add_note(request_id = int(fields['request_id']), text = fields['note_text'], user_id = current_user_id, passed_spam_filter = True) # Bypass spam filter because they are logged in. @@ -148,10 +150,10 @@ def add_link(request_id, url, description, user_id): return record_id return False -### @export "make_request" +### @export "make_request" def make_request(text, email = None, user_id = None, phone = None, alias = None, department = None, passed_spam_filter = False, offline_submission_type = None, date_received = None): """ Make the request. At minimum you need to communicate which record(s) you want, probably with some text.""" - if not passed_spam_filter: + if not passed_spam_filter: return None, False request_id = find_request(text) if request_id: # Same request already exists @@ -177,7 +179,7 @@ def make_request(text, email = None, user_id = None, phone = None, alias = None, generate_prr_emails(request_id, notification_type = "Request made", user_id = subscriber_user_id) # Send them an e-mail notification return request_id, True -### @export "add_subscriber" +### @export "add_subscriber" def add_subscriber(request_id, email): user_id = create_or_return_user(email = email) subscriber_id, is_new_subscriber = create_subscriber(request_id = request_id, user_id = user_id) @@ -186,7 +188,7 @@ def add_subscriber(request_id, email): return subscriber_id return False -### @export "ask_a_question" +### @export "ask_a_question" def ask_a_question(request_id, user_id, question): """ City staff can ask a question about a request they are confused about.""" req = get_obj("Request", request_id) @@ -211,12 +213,12 @@ def answer_a_question(qa_id, answer, subscriber_id = None, passed_spam_filter = generate_prr_emails(request_id = request_id, notification_type = "Question answered") return True -### @export "open_request" +### @export "open_request" def open_request(request_id): change_request_status(request_id, "Open") -### @export "assign_owner" -def assign_owner(request_id, reason, email = None): +### @export "assign_owner" +def assign_owner(request_id, reason, email = None): """ Called any time a new owner is assigned. This will overwrite the current owner.""" req = get_obj("Request", request_id) past_owner_id = None @@ -230,9 +232,9 @@ def assign_owner(request_id, reason, email = None): return owner_id app.logger.info("\n\nA new owner has been assigned: Owner: %s" % owner_id) - new_owner = get_obj("Owner", owner_id) + new_owner = get_obj("Owner", owner_id) # Update the associated department on request - update_obj(attribute = "department_id", val = new_owner.user.department, obj = req) + update_obj(attribute = "department_id", val = new_owner.user.department_id, obj = req) user_id = get_attribute(attribute = "user_id", obj_id = owner_id, obj_type = "Owner") # Send notifications if is_new_owner: @@ -278,7 +280,7 @@ def get_responses_chronologically(req): def set_directory_fields(): # Set basic user data if 'STAFF_URL' in app.config: - # This gets run at regular internals via db_users.py in order to keep the staff user list up to date. Before users are added/updated, ALL users get reset to 'inactive', and then only the ones in the current CSV are set to active. + # This gets run at regular internals via db_users.py in order to keep the staff user list up to date. Before users are added/updated, ALL users get reset to 'inactive', and then only the ones in the current CSV are set to active. for user in User.query.filter(User.is_staff == True).all(): update_user(user = user, is_staff = False) csvfile = urllib.urlopen(app.config['STAFF_URL']) @@ -296,7 +298,7 @@ def set_directory_fields(): else: app.logger.info("\n\n Please update the config variable LIAISONS_URL for where to find department liaison data for your agency.") else: - app.logger.info("\n\n Please update the config variable STAFF_URL for where to find csv data on the users in your agency.") + app.logger.info("\n\n Please update the config variable STAFF_URL for where to find csv data on the users in your agency.") if 'DEFAULT_OWNER_EMAIL' in app.config and 'DEFAULT_OWNER_REASON' in app.config: create_or_return_user(email = app.config['DEFAULT_OWNER_EMAIL'].lower(), alias = app.config['DEFAULT_OWNER_EMAIL'], department = app.config['DEFAULT_OWNER_REASON'], is_staff = True) app.logger.info("\n\n Creating a single user from DEFAULT_OWNER_EMAIL and DEFAULT_OWNER_REASON for now. You may log in with %s" %(app.config['DEFAULT_OWNER_EMAIL'])) diff --git a/public_records_portal/static/js/manage_request_city.js b/public_records_portal/static/js/manage_request_city.js index 3bb56d93..dcdac747 100644 --- a/public_records_portal/static/js/manage_request_city.js +++ b/public_records_portal/static/js/manage_request_city.js @@ -137,7 +137,7 @@ function prepareStaffTypeahead(){ $.each(user_data, function (i, staff) { emails.push(staff['email']); map[staff['alias'] + " - " + staff['email']] = staff['email']; - contacts.push(staff['alias'] + " - " + staff['email']); + contacts.push(staff['alias'] + " - " + staff['email']); }); }); return { map:map, emails:emails, contacts:contacts } diff --git a/public_records_portal/static/js/new_request.js b/public_records_portal/static/js/new_request.js index 24c70d25..2a3109d5 100644 --- a/public_records_portal/static/js/new_request.js +++ b/public_records_portal/static/js/new_request.js @@ -1,20 +1,34 @@ $(document).ready(function(){ + $.validator.addMethod('nopostdate', function(value, element) { + var eod = new Date(); + var dateval = new Date(value); + + eod.setHours(23, 59, 59, 999); + return this.optional(element) || dateval <= eod; + }, "This field must not be postdated."); + /* validates add a request form */ $("#submitRequest").validate({ rules: { request_text: { required: true, minlength: 2 - } - }, + }, + date_received: { + nopostdate: true + } + }, + messages: { + date_received: "You must select a date in the past." + }, highlight: function(element) { $(element).closest('.control-group').removeClass('success').addClass('error'); - }, + }, success: function(element) { element - .closest('.control-group').removeClass('error').addClass('success'); - } + .closest('.control-group').removeClass('error').addClass('success'); + } }); diff --git a/public_records_portal/templates/_response_widget.html b/public_records_portal/templates/_response_widget.html index 09cded08..f7e039c9 100644 --- a/public_records_portal/templates/_response_widget.html +++ b/public_records_portal/templates/_response_widget.html @@ -155,7 +155,7 @@

Reason for extension