mirror of
https://github.com/sartography/uva-covid19-testing-communicator.git
synced 2025-02-23 04:18:11 +00:00
Allow restricting results in main page, and downloading the results.
This commit is contained in:
parent
ee34a7ea20
commit
a8e897f344
@ -1,10 +1,13 @@
|
||||
import csv
|
||||
import io
|
||||
|
||||
import logging
|
||||
import os
|
||||
from functools import wraps
|
||||
|
||||
import connexion
|
||||
import sentry_sdk
|
||||
from flask import render_template, request, redirect, url_for, flash, abort
|
||||
from flask import render_template, request, redirect, url_for, flash, abort, Response, send_file
|
||||
from flask_assets import Environment
|
||||
from flask_cors import CORS
|
||||
from flask_mail import Mail
|
||||
@ -12,6 +15,7 @@ from flask_marshmallow import Marshmallow
|
||||
from flask_migrate import Migrate
|
||||
from flask_paginate import Pagination, get_page_parameter
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from pyparsing import unicode
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
from webassets import Bundle
|
||||
from flask_executor import Executor
|
||||
@ -98,23 +102,92 @@ def superuser(f):
|
||||
return decorated_function
|
||||
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
@superuser
|
||||
def index():
|
||||
from communicator.models import Sample
|
||||
from communicator.tables import SampleTable
|
||||
# display results
|
||||
page = request.args.get(get_page_parameter(), type=int, default=1)
|
||||
samples = db.session.query(Sample).order_by(Sample.date.desc())
|
||||
pagination = Pagination(page=page, total=samples.count(), search=False, record_name='samples')
|
||||
|
||||
table = SampleTable(samples.paginate(page,10,error_out=False).items)
|
||||
return render_template(
|
||||
'index.html',
|
||||
table=table,
|
||||
pagination=pagination,
|
||||
base_href=BASE_HREF
|
||||
)
|
||||
download = False
|
||||
|
||||
form = forms.SearchForm(request.form)
|
||||
action = BASE_HREF + "/"
|
||||
samples = db.session.query(Sample).order_by(Sample.date.desc())
|
||||
if request.method == 'POST' and form.validate():
|
||||
if form.startDate.data:
|
||||
samples = samples.filter(Sample.date >= form.startDate.data)
|
||||
if form.endDate.data:
|
||||
samples = samples.filter(Sample.date <= form.endDate.data)
|
||||
if form.studentId.data:
|
||||
samples = samples.filter(Sample.student_id == form.studentId.data)
|
||||
if form.location.data:
|
||||
samples = samples.filter(Sample.location == form.location.data)
|
||||
if form.download.data:
|
||||
download = True
|
||||
|
||||
# display results
|
||||
if download:
|
||||
csv = __make_csv(samples)
|
||||
return send_file(csv, attachment_filename='data_export.csv', as_attachment=True)
|
||||
|
||||
else:
|
||||
page = request.args.get(get_page_parameter(), type=int, default=1)
|
||||
pagination = Pagination(page=page, total=samples.count(), search=False, record_name='samples')
|
||||
|
||||
table = SampleTable(samples.paginate(page,10,error_out=False).items)
|
||||
|
||||
return render_template(
|
||||
'index.html',
|
||||
form=form,
|
||||
table=table,
|
||||
action=action,
|
||||
pagination=pagination,
|
||||
base_href=BASE_HREF,
|
||||
description_map={}
|
||||
)
|
||||
|
||||
|
||||
def __make_csv(sample_query):
|
||||
csvfile = io.StringIO()
|
||||
headers = [
|
||||
'barcode',
|
||||
'student_id',
|
||||
'date',
|
||||
'location',
|
||||
'phone',
|
||||
'email',
|
||||
'result_code',
|
||||
'ivy_file',
|
||||
'email_notified',
|
||||
'text_notified'
|
||||
]
|
||||
writer = csv.DictWriter(csvfile, headers)
|
||||
writer.writeheader()
|
||||
for sample in sample_query.all():
|
||||
writer.writerow(
|
||||
{
|
||||
'barcode': sample.barcode,
|
||||
'student_id': sample.student_id,
|
||||
'date': sample.date,
|
||||
'location': sample.location,
|
||||
'phone': sample.phone,
|
||||
'email': sample.email,
|
||||
'result_code': sample.result_code,
|
||||
'ivy_file': sample.ivy_file,
|
||||
'email_notified': sample.email_notified,
|
||||
'text_notified': sample.text_notified,
|
||||
}
|
||||
)
|
||||
|
||||
# Creating the byteIO object from the StringIO Object
|
||||
mem = io.BytesIO()
|
||||
mem.write(csvfile.getvalue().encode('utf-8'))
|
||||
# seeking was necessary. Python 3.5.2, Flask 0.12.2
|
||||
mem.seek(0)
|
||||
csvfile.close()
|
||||
return mem
|
||||
|
||||
|
||||
|
||||
@app.route('/invitation', methods=['GET', 'POST'])
|
||||
@superuser
|
||||
|
@ -112,7 +112,8 @@ def _notify_by_text(file_name=None, retry=False):
|
||||
if file_name:
|
||||
sample_query = sample_query.filter(Sample.ivy_file == file_name)
|
||||
|
||||
sample_query = sample_query.limit(150) # Only send out 150 texts at a time.
|
||||
# Do not limit texts, as errors pile up we end up sending less and less, till none go out.
|
||||
# sample_query = sample_query.limit(150) # Only send out 150 texts at a time.
|
||||
samples = sample_query.all()
|
||||
for sample in samples:
|
||||
last_failure = sample.last_failure_by_type(TEXT_TYPE)
|
||||
|
@ -3,7 +3,7 @@ import re
|
||||
from flask_table import Table, Col, LinkCol, BoolCol, DatetimeCol, NestedTableCol
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import SelectMultipleField, StringField, BooleanField, SelectField, validators, HiddenField, TextAreaField, \
|
||||
ValidationError
|
||||
ValidationError, DateField
|
||||
from wtforms.widgets import TextArea
|
||||
|
||||
|
||||
@ -18,3 +18,11 @@ class InvitationForm(FlaskForm):
|
||||
for email in all_emails:
|
||||
if not re.search(EMAIL_REGEX, email):
|
||||
raise ValidationError(f'Invalid email \'{email}\', Emails must each be on a seperate line.')
|
||||
|
||||
|
||||
class SearchForm(FlaskForm):
|
||||
startDate = DateField("Start Date (YYYY-MM-DD)", validators=[validators.Optional()])
|
||||
endDate = DateField("End Date (YYYY-MM-DD)", validators=[validators.Optional()])
|
||||
studentId = TextAreaField('Student Id')
|
||||
location = TextAreaField('Location')
|
||||
download = BooleanField('Download Results')
|
||||
|
@ -114,7 +114,7 @@ select.multi {
|
||||
.form-field {
|
||||
display: flex;
|
||||
max-width: 100vw;
|
||||
margin-bottom: 40px;
|
||||
margin-bottom: 5px;
|
||||
padding: 2em; }
|
||||
.form-field.hidden {
|
||||
display: none; }
|
||||
|
@ -215,7 +215,7 @@ select.multi {
|
||||
.form-field {
|
||||
display: flex;
|
||||
max-width: 100vw;
|
||||
margin-bottom: 40px;
|
||||
margin-bottom: 5px;
|
||||
padding: 2em;
|
||||
|
||||
&.hidden {
|
||||
|
@ -15,6 +15,23 @@
|
||||
<h2>UVA Be Safe Communicator</h2>
|
||||
<a href="{{base_href + '/invitation'}}">Send Invitations</a> | <a href="{{base_href + '/imported_files'}}">View imported files</a>
|
||||
|
||||
<form action="{{ action }}" method="post">
|
||||
|
||||
{{ form.csrf_token() }}
|
||||
{% for field in form if field.name != "csrf_token" %}
|
||||
<div class="form-field {{ field.widget.input_type }}">
|
||||
<div class="form-field-label">{{ field.label() }}:</div>
|
||||
<div class="form-field-input">{{ field }}</div>
|
||||
<div class="form-field-help">{{ description_map[field.name] }}</div>
|
||||
{% for error in field.errors %}
|
||||
<div class="form-field-error">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<button class="btn btn-primary" type="submit">Submit</button>
|
||||
<a href="{{ url_for('index') }}" class="btn btn-default">Cancel</a>
|
||||
</form>
|
||||
|
||||
<h3>Records to be processed</h3>
|
||||
{{ pagination.info }}
|
||||
{{ pagination.links }}
|
||||
|
Loading…
x
Reference in New Issue
Block a user