From 89513af93926ca97ef33422641999a702f1201de Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Tue, 6 Dec 2022 13:39:04 -0500 Subject: [PATCH] admin: Sending bulk emails --- goathacks/__init__.py | 5 ++ goathacks/admin/__init__.py | 54 +++++++++++++++++- goathacks/registration/__init__.py | 28 +++++++-- goathacks/registration/forms.py | 7 ++- goathacks/static/css/style.css | 12 ++++ goathacks/static/css/style.scss | 12 +++- goathacks/static/img/favicon.png | Bin 0 -> 12787 bytes goathacks/templates/dashboard.html | 44 ++------------ goathacks/templates/emails/dropped.txt | 13 +++++ goathacks/templates/emails/registration.txt | 19 ++++++ .../templates/emails/waitlist_promotion.txt | 18 ++++++ goathacks/templates/mail.html | 6 +- goathacks/templates/register.html | 12 ++++ requirements.txt | 1 + 14 files changed, 178 insertions(+), 53 deletions(-) create mode 100644 goathacks/static/img/favicon.png create mode 100644 goathacks/templates/emails/dropped.txt create mode 100644 goathacks/templates/emails/registration.txt create mode 100644 goathacks/templates/emails/waitlist_promotion.txt diff --git a/goathacks/__init__.py b/goathacks/__init__.py index d6967a9..455a979 100644 --- a/goathacks/__init__.py +++ b/goathacks/__init__.py @@ -4,6 +4,7 @@ from flask_migrate import Migrate from flask_login import LoginManager from flask_assets import Bundle, Environment from flask_cors import CORS +from flask_mail import Mail db = SQLAlchemy() @@ -11,6 +12,7 @@ migrate = Migrate() login = LoginManager() environment = Environment() cors = CORS() +mail = Mail() def create_app(): app = Flask(__name__) @@ -22,6 +24,7 @@ def create_app(): login.init_app(app) environment.init_app(app) cors.init_app(app) + mail.init_app(app) scss = Bundle('css/style.scss', filters='scss', output='css/style.css') @@ -38,3 +41,5 @@ def create_app(): app.register_blueprint(admin.bp) return app + + diff --git a/goathacks/admin/__init__.py b/goathacks/admin/__init__.py index 89f035f..58dafea 100644 --- a/goathacks/admin/__init__.py +++ b/goathacks/admin/__init__.py @@ -1,11 +1,12 @@ -from flask import Blueprint, jsonify, redirect, render_template, url_for +from flask import Blueprint, jsonify, redirect, render_template, request, url_for from flask_login import current_user, login_required +from flask_mail import Message from goathacks.models import User bp = Blueprint("admin", __name__, url_prefix="/admin") -from goathacks import db +from goathacks import db,mail @bp.route("/") @login_required @@ -53,6 +54,45 @@ def home(): female_count=female_count, nb_count=nb_count, check_in_count=check_in_count, schools=schools) +@bp.route("/mail") +@login_required +def mail(): + if not current_user.is_admin: + return redirect(url_for("dashboard.home")) + + total_count = len(db.session.execute(db.select(User)).scalars().all()) + + return render_template("mail.html", NUM_HACKERS=total_count) + +@bp.route("/send", methods=["POST"]) +@login_required +def send(): + if not current_user.is_admin: + return {"status": "error"} + + json = request.json + + users = User.query.all() + + to = [] + if json["recipients"] == "org": + to = ["hack@wpi.edu"] + elif json['recipients'] == 'admin': + to = ["acm-sysadmin@wpi.edu"] + elif json['recipients'] == "all": + to = [x['email'] for x in users] + + with mail.connect() as conn: + for e in to: + msg = Message(json['subject']) + msg.add_recipient(e) + msg.html = json['html'] + msg.body = json['text'] + + conn.send(msg) + + return {"status": "success"} + @bp.route("/check_in/") @login_required def check_in(id): @@ -79,6 +119,11 @@ def drop(id): if user.checked_in: return {"status": "error", "msg": "Hacker is already checked in"} + msg = Message("Application Dropped") + msg.add_recipient(user.email) + msg.sender = ("GoatHacks Team", "hack@wpi.edu") + msg.body = render_template("emails/dropped.txt", user=user) + db.session.delete(user) db.session.commit() @@ -122,6 +167,11 @@ def promote_waitlist(id): user.waitlisted = False db.session.commit() + msg = Message("Waitlist Promotion") + msg.add_recipient(user.email) + msg.sender = ("GoatHacks Team", "hack@wpi.edu") + msg.body = render_template("emails/waitlist_promotion.txt", user=user) + return {"status": "success"} @bp.route("/hackers.csv") diff --git a/goathacks/registration/__init__.py b/goathacks/registration/__init__.py index 3ddd699..3a9833d 100644 --- a/goathacks/registration/__init__.py +++ b/goathacks/registration/__init__.py @@ -1,5 +1,5 @@ from datetime import datetime -from flask import Blueprint, flash, redirect, render_template, request, url_for +from flask import Blueprint, config, current_app, flash, redirect, render_template, request, url_for import flask_login from flask_login import current_user from goathacks.registration.forms import LoginForm, RegisterForm @@ -14,8 +14,10 @@ bp = Blueprint('registration', __name__, url_prefix="/registration") def register(): if current_user.is_authenticated: flash("You are already registered and logged in!") + print("got register") form = RegisterForm(request.form) + print(vars(form.gender)) if request.method == 'POST': print("Got form") email = request.form.get('email') @@ -23,22 +25,32 @@ def register(): last_name = request.form.get('last_name') password = request.form.get('password') password_c = request.form.get('password_confirm') + school = request.form.get('school') + phone = request.form.get('phone_number') + gender = request.form.get('gender') if password == password_c: # Passwords match! + + # Count of all non-waitlisted hackers + num_not_waitlisted = len(User.query.filter_by(waitlisted=False).all()) + waitlisted = False + print(num_not_waitlisted) + print(current_app.config['MAX_BEFORE_WAITLIST']) + if num_not_waitlisted >= current_app.config['MAX_BEFORE_WAITLIST']: + waitlisted = True user = User( email=email, password=generate_password_hash(password), first_name=first_name, last_name=last_name, last_login=datetime.now(), + waitlisted=waitlisted, + school=school, + phone=phone, + gender=gender ) - - # Count of all non-waitlisted hackers - # num_not_waitlisted = len(db.session.execute(db.select(User).filter(waitlisted=False)).scalars().all()) - # print(num_not_waitlisted) - db.session.add(user) db.session.commit() flask_login.login_user(user) @@ -48,3 +60,7 @@ def register(): flash("Passwords do not match") return render_template("register.html", form=form) + +@bp.route("/login", methods=["GET", "POST"]) +def login(): + return "OK" diff --git a/goathacks/registration/forms.py b/goathacks/registration/forms.py index 08f7fd1..3fe74c5 100644 --- a/goathacks/registration/forms.py +++ b/goathacks/registration/forms.py @@ -1,5 +1,5 @@ from flask_wtf import FlaskForm -from wtforms import BooleanField, PasswordField, StringField, SubmitField +from wtforms import BooleanField, PasswordField, SelectField, StringField, SubmitField, widgets from wtforms.validators import DataRequired class RegisterForm(FlaskForm): @@ -10,6 +10,11 @@ class RegisterForm(FlaskForm): password = PasswordField("Password", validators=[DataRequired()]) password_confirm = PasswordField("Confirm Password", validators=[DataRequired()]) + school = StringField("School/University", validators=[DataRequired()]) + phone_number = StringField("Phone number", validators=[DataRequired()]) + gender = SelectField("Gender", choices=[("F", "Female"), ("M", "Male"), + ("NB", "Non-binary/Other")], + widget=widgets.Select()) agree_coc = BooleanField("I confirm that I have read and agree to the Code of Conduct", validators=[DataRequired()]) submit = SubmitField("Register") diff --git a/goathacks/static/css/style.css b/goathacks/static/css/style.css index 7383868..68108c1 100644 --- a/goathacks/static/css/style.css +++ b/goathacks/static/css/style.css @@ -150,8 +150,20 @@ form input[type="radio"] { padding-right: 5px; } +form input[type="checkbox"]:checked { + visibility: visible; + left: unset; + position: unset; +} + form label { font-size: 1.1rem; padding-right: 10px; padding-left: 25px !important; } + +form select { + display: unset; + background: #974355; + max-width: 11rem; +} diff --git a/goathacks/static/css/style.scss b/goathacks/static/css/style.scss index 1b8da94..92088ee 100644 --- a/goathacks/static/css/style.scss +++ b/goathacks/static/css/style.scss @@ -1,6 +1,6 @@ $color-bg: #003049; $color-fg: #eee; -$color-section-bg: #F5665B; +$color-section-bg: #974355; $color-accent: #26a69a; @font-face {font-family: "Krungthep"; src: url("//db.onlinewebfonts.com/t/736cf5b08b01082a3645e14038868e20.eot"); src: url("//db.onlinewebfonts.com/t/736cf5b08b01082a3645e14038868e20.eot?#iefix") format("embedded-opentype"), url("//db.onlinewebfonts.com/t/736cf5b08b01082a3645e14038868e20.woff2") format("woff2"), url("//db.onlinewebfonts.com/t/736cf5b08b01082a3645e14038868e20.woff") format("woff"), url("//db.onlinewebfonts.com/t/736cf5b08b01082a3645e14038868e20.ttf") format("truetype"), url("//db.onlinewebfonts.com/t/736cf5b08b01082a3645e14038868e20.svg#Krungthep") format("svg"); } @@ -148,9 +148,19 @@ form { input[type="radio"] { padding-right: 5px; } + input[type="checkbox"]:checked { + visibility: visible; + left: unset; + position: unset; + } label { font-size: 1.1rem; padding-right: 10px; padding-left: 25px !important; } + select { + display: unset; + background: $color-section-bg; + max-width: 11rem; + } } diff --git a/goathacks/static/img/favicon.png b/goathacks/static/img/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..39a081ff4eec02d3bcd06da2756223f37761a65b GIT binary patch literal 12787 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 - - \ No newline at end of file + diff --git a/goathacks/templates/register.html b/goathacks/templates/register.html index 25949ad..e3e45f1 100644 --- a/goathacks/templates/register.html +++ b/goathacks/templates/register.html @@ -29,6 +29,18 @@
{{form.last_name}}
{{form.last_name.label}}
+
+

Miscellaneous Information

+
+ {{form.phone_number}}
{{form.phone_number.label}} +
+
+ {{form.school}}
{{form.school.label}} +
+
+ {{form.gender.label}}{{form.gender}} +
+
diff --git a/requirements.txt b/requirements.txt index 1ccc43f..a1a0a95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ click==8.1.3 Flask==2.2.2 Flask-Assets Flask-CORS +Flask-Mail Flask-Login==0.6.2 Flask-Migrate==4.0.0 Flask-SQLAlchemy==3.0.2