mirror of
https://github.com/sartography/uva-covid19-testing-communicator.git
synced 2025-02-23 04:18:11 +00:00
record what invitations were sent so far, and perform some basic validation on the email list provided before sending and recording.
This commit is contained in:
parent
523b68978c
commit
d6b639ca23
@ -14,7 +14,6 @@ from flask_sqlalchemy import SQLAlchemy
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
from webassets import Bundle
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# API, fully defined in api.yml
|
||||
@ -100,18 +99,29 @@ def index():
|
||||
|
||||
@app.route('/invitation', methods=['GET', 'POST'])
|
||||
def send_invitation():
|
||||
from communicator.models.invitation import Invitation
|
||||
from communicator.tables import InvitationTable
|
||||
|
||||
form = forms.InvitationForm(request.form)
|
||||
action = BASE_HREF + "/invitation"
|
||||
title = "Send invitation to students"
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.method == 'POST' and form.validate():
|
||||
from communicator.services.notification_service import NotificationService
|
||||
with NotificationService(app) as ns:
|
||||
ns.send_invitations(form.date.data, form.location.data, form.emails.data)
|
||||
|
||||
# display results
|
||||
page = request.args.get(get_page_parameter(), type=int, default=1)
|
||||
invites = db.session.query(Invitation).order_by(Invitation.date.desc())
|
||||
pagination = Pagination(page=page, total=invites.count(), search=False, record_name='samples')
|
||||
|
||||
table = InvitationTable(invites.paginate(page,10,error_out=False).items)
|
||||
|
||||
return render_template(
|
||||
'form.html',
|
||||
form=form,
|
||||
table=table,
|
||||
pagination=pagination,
|
||||
action=action,
|
||||
title=title,
|
||||
description_map={},
|
||||
|
@ -1,10 +1,20 @@
|
||||
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
|
||||
from wtforms import SelectMultipleField, StringField, BooleanField, SelectField, validators, HiddenField, TextAreaField, \
|
||||
ValidationError
|
||||
from wtforms.widgets import TextArea
|
||||
|
||||
|
||||
class InvitationForm(FlaskForm):
|
||||
location = StringField('Location (ex. Newcomb Hall, South Meeting Room)', [validators.DataRequired()])
|
||||
date = StringField('Date (ex. Monday, September 23 from 10:00 am-5:00 pm ', [validators.DataRequired()])
|
||||
emails = TextAreaField('Emails (newline delimited)', render_kw={'rows': 50, 'cols': 50})
|
||||
emails = TextAreaField('Emails (newline delimited)', render_kw={'rows': 10, 'cols': 50})
|
||||
|
||||
def validate_emails(form, field):
|
||||
all_emails = field.data.splitlines()
|
||||
EMAIL_REGEX = re.compile('^[a-z0-9]+[._a-z0-9]+[@]\w+[.]\w{2,3}$')
|
||||
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.')
|
||||
|
11
communicator/models/invitation.py
Normal file
11
communicator/models/invitation.py
Normal file
@ -0,0 +1,11 @@
|
||||
from sqlalchemy import func
|
||||
|
||||
from communicator import db
|
||||
|
||||
|
||||
class Invitation(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
date_sent = db.Column(db.DateTime(timezone=True), default=func.now())
|
||||
location = db.Column(db.String)
|
||||
date = db.Column(db.String)
|
||||
total_recipients = db.Column(db.Integer)
|
@ -3,12 +3,14 @@ import uuid
|
||||
from email.header import Header
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
import re
|
||||
|
||||
from flask import render_template
|
||||
from twilio.rest import Client
|
||||
|
||||
from communicator import app
|
||||
from communicator import app, db
|
||||
from communicator.errors import CommError
|
||||
from communicator.models.invitation import Invitation
|
||||
|
||||
TEST_MESSAGES = []
|
||||
|
||||
@ -86,6 +88,10 @@ class NotificationService(object):
|
||||
|
||||
self._send_email(subject, recipients=[self.sender], bcc=emails, text_body=text_body, html_body=html_body)
|
||||
|
||||
invitation_log = Invitation(location=location, date=date, total_recipients=len(emails))
|
||||
db.session.add(invitation_log)
|
||||
db.session.commit()
|
||||
|
||||
def _tracking_code(self):
|
||||
return str(uuid.uuid4())[:16]
|
||||
|
||||
|
@ -18,3 +18,12 @@ class IvyFileTable(Table):
|
||||
file_name = Col('File Name')
|
||||
date_added = DatetimeCol('Date', "medium")
|
||||
sample_count = Col('Total Records')
|
||||
|
||||
|
||||
class InvitationTable(Table):
|
||||
def sort_url(self, col_id, reverse=False):
|
||||
pass
|
||||
date_sent = DatetimeCol('Date Sent', "medium")
|
||||
location = Col('Location')
|
||||
date = Col('Date')
|
||||
total_recipients = Col('# Recipients')
|
||||
|
@ -6,6 +6,11 @@
|
||||
<base href="/">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://use.typekit.net/kwp6dli.css">
|
||||
<link rel="shortcut icon" href="{{ base_href + url_for('static', filename='favicon.ico') }}">
|
||||
{% assets 'app_scss' %}
|
||||
<link href="{{ base_href + ASSET_URL }}" rel="stylesheet" type="text/css">
|
||||
{% endassets %}
|
||||
<link rel="shortcut icon" href="{{ base_href + url_for('static', filename='favicon.ico') }}">
|
||||
</head>
|
||||
<style type="text/css">
|
||||
input {
|
||||
@ -14,6 +19,14 @@
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<a href="{{base_href}}"><< Home</a>
|
||||
|
||||
<h3>Previous Entries</h3>
|
||||
{{ pagination.info }}
|
||||
{{ pagination.links }}
|
||||
{{ table }}
|
||||
{{ pagination.links }}
|
||||
|
||||
<h2>{{ title }}</h2>
|
||||
<p>{{ details|safe }}</p>
|
||||
<form action="{{ action }}" method="post">
|
||||
@ -34,5 +47,6 @@
|
||||
<a href="{{ url_for('index') }}" class="btn btn-default">Cancel</a>
|
||||
</form>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -12,6 +12,8 @@
|
||||
<link rel="shortcut icon" href="{{ base_href + url_for('static', filename='favicon.ico') }}">
|
||||
</head>
|
||||
<body>
|
||||
<a href="{{base_href}}"><< Home</a>
|
||||
|
||||
<h2>UVA Be Safe Communicator</h2>
|
||||
|
||||
<h3>The following files were imported from IVY</h3>
|
||||
|
@ -13,6 +13,8 @@
|
||||
</head>
|
||||
<body>
|
||||
<h2>UVA Be Safe Communicator</h2>
|
||||
<a href="invitation">Send Invitations</a> | <a href="imported_files">View imported files</a>
|
||||
|
||||
|
||||
<h3>Records to be processed</h3>
|
||||
{{ pagination.info }}
|
||||
|
35
migrations/versions/3525dcf128cb_.py
Normal file
35
migrations/versions/3525dcf128cb_.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 3525dcf128cb
|
||||
Revises: 39a845579587
|
||||
Create Date: 2020-09-23 16:23:09.383030
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3525dcf128cb'
|
||||
down_revision = '39a845579587'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('invitation',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('date_sent', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('location', sa.String(), nullable=True),
|
||||
sa.Column('date', sa.String(), nullable=True),
|
||||
sa.Column('total_recipients', sa.Integer(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('invitation')
|
||||
# ### end Alembic commands ###
|
28
migrations/versions/39a845579587_.py
Normal file
28
migrations/versions/39a845579587_.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 39a845579587
|
||||
Revises: fa4b41e0bfe6
|
||||
Create Date: 2020-09-23 15:50:44.987745
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '39a845579587'
|
||||
down_revision = 'fa4b41e0bfe6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('ivy_file', sa.Column('sample_count', sa.Integer(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('ivy_file', 'sample_count')
|
||||
# ### end Alembic commands ###
|
Loading…
x
Reference in New Issue
Block a user