From 7fb7270659eb1da28c2640915e660f973dd79561 Mon Sep 17 00:00:00 2001 From: Ram Zallan Date: Wed, 19 Jul 2017 13:06:13 -0400 Subject: [PATCH 1/4] Work on Become Active functionality --- conditional/blueprints/dashboard.py | 3 ++ conditional/blueprints/member_management.py | 20 +++++++++++++ conditional/templates/dashboard.html | 9 ++++++ frontend/javascript/modules/becomeActive.js | 31 +++++++++++++++++++++ frontend/stylesheets/pages/_dashboard.scss | 7 +++++ 5 files changed, 70 insertions(+) create mode 100644 frontend/javascript/modules/becomeActive.js diff --git a/conditional/blueprints/dashboard.py b/conditional/blueprints/dashboard.py index c598101f..9ca7b8ad 100644 --- a/conditional/blueprints/dashboard.py +++ b/conditional/blueprints/dashboard.py @@ -5,6 +5,7 @@ from conditional.util.ldap import ldap_is_onfloor from conditional.util.ldap import ldap_is_active from conditional.util.ldap import ldap_is_intromember +from conditional.util.ldap import ldap_is_current_student from conditional.util.ldap import ldap_get_member from conditional.util.ldap import ldap_get_active_members @@ -44,6 +45,8 @@ def display_dashboard(): data['active'] = ldap_is_active(member) data['onfloor'] = ldap_is_onfloor(member) data['voting'] = bool(member.uid in can_vote) + data['student'] = ldap_is_current_student(member) + data['voting_count'] = {"Voting Members": len(can_vote), "Active Members": len(ldap_get_active_members())} diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index 98972179..cc262c80 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -28,6 +28,7 @@ from conditional.util.ldap import ldap_is_financial_director from conditional.util.ldap import ldap_is_active from conditional.util.ldap import ldap_is_onfloor +from conditional.util.ldap import ldap_is_current_student from conditional.util.ldap import ldap_set_roomnumber from conditional.util.ldap import ldap_set_active from conditional.util.ldap import ldap_set_inactive @@ -491,6 +492,25 @@ def member_management_upgrade_user(): return jsonify({"success": True}), 200 +@member_management_bp.route('/manage/make_user_active', methods=['POST']) +def member_management_make_user_active(): + log = logger.new(request=request) + + post_data = request.get_json() + + uid = post_data['uid'] + account = ldap_get_member(uid) + + if not ldap_is_current_student(account) or ldap_is_active(account): + return jsonify({"success": False}), 403 + + ldap_set_active(account) + log.info("Make user {} active".format(uid)) + + clear_members_cache() + return jsonify({"success": True}), 200 + + @member_management_bp.route('/manage/intro_project', methods=['GET']) def introductory_project(): log = logger.new(request=request) diff --git a/conditional/templates/dashboard.html b/conditional/templates/dashboard.html index e2cc29ab..9bb7d4f2 100644 --- a/conditional/templates/dashboard.html +++ b/conditional/templates/dashboard.html @@ -251,7 +251,16 @@

Major Projects

{% endif %} + {% if student and not active %} +
+ Hey there! You're eligible to become an active member, click the button below if you'd like to pay dues and become active! +
+ Become Active +
+ {% endif %} + +
{% if housing %}
diff --git a/frontend/javascript/modules/becomeActive.js b/frontend/javascript/modules/becomeActive.js new file mode 100644 index 00000000..b87067a2 --- /dev/null +++ b/frontend/javascript/modules/becomeActive.js @@ -0,0 +1,31 @@ +import FetchUtil from "../utils/fetchUtil"; + +export default class becomeActive { + constructor(link) { + this.link = link; + this.uid = this.link.dataset.uid; + this.endpoint = '/manage/make_user_active'; + this.render(); + } + + render() { + this.link.addEventListener('click', e => this._delete(e)); + } + + _delete(e) { + e.preventDefault(); + + let payload = { + uid: this.uid + }; + + FetchUtil.postWithWarning(this.endpoint, payload, { + warningText: "Becoming an active member means that you will be charged" + + " dues, which are $80 per semester.", + successText: "You are now an active member." + }, () => { + document.getElementById('becomeActive').remove(); + }); + } +} + diff --git a/frontend/stylesheets/pages/_dashboard.scss b/frontend/stylesheets/pages/_dashboard.scss index debdba53..85ca03d5 100644 --- a/frontend/stylesheets/pages/_dashboard.scss +++ b/frontend/stylesheets/pages/_dashboard.scss @@ -41,3 +41,10 @@ margin: 5px 0; } } + +.alert-info { + .btn { + margin-top: 10px; + } +} + From 8e625205189724439013b0d79a52b71eab8c7270 Mon Sep 17 00:00:00 2001 From: Ram Zallan Date: Wed, 19 Jul 2017 14:56:55 -0400 Subject: [PATCH 2/4] Change alert to panel-info --- conditional/templates/dashboard.html | 14 ++++++++++---- frontend/stylesheets/pages/_dashboard.scss | 7 ------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/conditional/templates/dashboard.html b/conditional/templates/dashboard.html index 9bb7d4f2..622cb3ed 100644 --- a/conditional/templates/dashboard.html +++ b/conditional/templates/dashboard.html @@ -252,10 +252,16 @@

Major Projects

{% endif %} {% if student and not active %} -
- Hey there! You're eligible to become an active member, click the button below if you'd like to pay dues and become active! -
- Become Active +
+
+

Become Active

+
+
+ Hey there, you're eligible to become an active member! Click the button below if you'd like to become active and pay dues. +
+
{% endif %} diff --git a/frontend/stylesheets/pages/_dashboard.scss b/frontend/stylesheets/pages/_dashboard.scss index 85ca03d5..debdba53 100644 --- a/frontend/stylesheets/pages/_dashboard.scss +++ b/frontend/stylesheets/pages/_dashboard.scss @@ -41,10 +41,3 @@ margin: 5px 0; } } - -.alert-info { - .btn { - margin-top: 10px; - } -} - From 297c766ae77d3526d3a2dddc28d2abf7f810b1f1 Mon Sep 17 00:00:00 2001 From: Ram Zallan Date: Mon, 24 Jul 2017 19:45:05 -0400 Subject: [PATCH 3/4] Implement Become Active and Export All Active functionality --- conditional/blueprints/member_management.py | 62 +++++++++++++++++-- conditional/models/models.py | 2 + conditional/templates/dashboard.html | 4 +- conditional/templates/member_management.html | 16 ++++- conditional/util/flask.py | 4 ++ .../modules/acceptDuesDatepicker.js | 58 +++++++++++++++++ frontend/javascript/modules/becomeActive.js | 7 +-- frontend/stylesheets/pages/_management.scss | 11 ++++ migrations/versions/d1a06ab54211_.py | 26 ++++++++ 9 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 frontend/javascript/modules/acceptDuesDatepicker.js create mode 100644 migrations/versions/d1a06ab54211_.py diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index cc262c80..dc1be6d0 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -1,11 +1,12 @@ import csv import io +import re from datetime import datetime import structlog -from flask import Blueprint, request, jsonify, abort +from flask import Blueprint, request, jsonify, abort, make_response from conditional.models.models import FreshmanAccount from conditional.models.models import FreshmanEvalData @@ -88,9 +89,11 @@ def display_member_management(): if settings: lockdown = settings.site_lockdown intro_form = settings.intro_form_active + accept_dues_until = settings.accept_dues_until else: lockdown = False intro_form = False + accept_dues_until = datetime.now() return render_template(request, "member_management.html", username=username, @@ -102,6 +105,7 @@ def display_member_management(): freshmen=freshmen_list, co_op=co_op_list, site_lockdown=lockdown, + accept_dues_until=accept_dues_until, intro_form=intro_form) @@ -136,6 +140,31 @@ def member_management_eval(): return jsonify({"success": True}), 200 +@member_management_bp.route('/manage/accept_dues_until', methods=['PUT']) +def member_management_financial(): + log = logger.new(request=request) + + username = request.headers.get('x-webauth-user') + account = ldap_get_member(username) + + if not ldap_is_financial_director(account): + return "must be financial director", 403 + + post_data = request.get_json() + + if 'acceptDuesUntil' in post_data: + date = datetime.strptime(post_data['acceptDuesUntil'], "%Y-%m-%d") + log.info('Changed Dues Accepted Until: {}'.format(date)) + EvalSettings.query.update( + { + 'accept_dues_until': date + }) + + db.session.flush() + db.session.commit() + return jsonify({"success": True}), 200 + + @member_management_bp.route('/manage/user', methods=['POST']) def member_management_adduser(): log = logger.new(request=request) @@ -496,13 +525,11 @@ def member_management_upgrade_user(): def member_management_make_user_active(): log = logger.new(request=request) - post_data = request.get_json() - - uid = post_data['uid'] + uid = request.headers.get('x-webauth-user') account = ldap_get_member(uid) if not ldap_is_current_student(account) or ldap_is_active(account): - return jsonify({"success": False}), 403 + return "must be current student and not active", 403 ldap_set_active(account) log.info("Make user {} active".format(uid)) @@ -603,6 +630,31 @@ def clear_active_members(): return jsonify({"success": True}), 200 +@member_management_bp.route('/manage/export_active_list', methods=['GET']) +def export_active_list(): + sio = io.StringIO() + csvw = csv.writer(sio) + + active_list = [["Full Name", "RIT Username", "Amount to Charge"]] + for member in ldap_get_active_members(): + full_name = member.cn + rit_username = re.search(".*uid=(\\w*)", member.ritDn).group(1) + will_coop = CurrentCoops.query.filter( + CurrentCoops.date_created > start_of_year(), + CurrentCoops.uid == member.uid).first() + if will_coop: + dues = 80 + else: + dues = 160 + active_list.append([full_name, rit_username, dues]) + + csvw.writerows(active_list) + output = make_response(sio.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=csh_active_list.csv" + output.headers["Content-type"] = "text/csv" + return output + + @member_management_bp.route('/manage/current/', methods=['POST', 'DELETE']) def remove_current_student(uid): log = logger.new(request=request) diff --git a/conditional/models/models.py b/conditional/models/models.py index aecbdac6..cd4d4563 100644 --- a/conditional/models/models.py +++ b/conditional/models/models.py @@ -243,11 +243,13 @@ class EvalSettings(db.Model): housing_form_active = Column(Boolean) intro_form_active = Column(Boolean) site_lockdown = Column(Boolean) + accept_dues_until = Column(Date) def __init__(self): self.housing_form_active = True self.intro_form_active = True self.site_lockdown = False + self.accept_dues_until = datetime.now() class SpringEval(db.Model): diff --git a/conditional/templates/dashboard.html b/conditional/templates/dashboard.html index 622cb3ed..dee32941 100644 --- a/conditional/templates/dashboard.html +++ b/conditional/templates/dashboard.html @@ -251,7 +251,7 @@

Major Projects

{% endif %} - {% if student and not active %} + {% if accepting_dues and student and not active %}

Become Active

@@ -260,7 +260,7 @@

Become Active

Hey there, you're eligible to become an active member! Click the button below if you'd like to become active and pay dues.
{% endif %} diff --git a/conditional/templates/member_management.html b/conditional/templates/member_management.html index 6235978f..6a96d5de 100644 --- a/conditional/templates/member_management.html +++ b/conditional/templates/member_management.html @@ -10,7 +10,7 @@

Administration

-
+
{{num_current}}
Current Students
@@ -37,6 +37,11 @@

Administration

+ {% else %} +
+ + +
{% endif %}
@@ -166,7 +171,14 @@

Freshmen Management

-

Member Management

+

+ Member Management + + + +

diff --git a/conditional/util/flask.py b/conditional/util/flask.py index 4d1e356f..19534743 100644 --- a/conditional/util/flask.py +++ b/conditional/util/flask.py @@ -1,3 +1,5 @@ +from datetime import date + from flask import render_template as flask_render_template from conditional.models.models import EvalSettings @@ -27,6 +29,7 @@ def render_template(request, template_name, **kwargs): db.session.commit() account = ldap_get_member(user_name) lockdown = EvalSettings.query.first().site_lockdown + accepting_dues = EvalSettings.query.first().accept_dues_until > date.today() is_active = ldap_is_active(account) is_alumni = ldap_is_alumni(account) is_eboard = ldap_is_eboard(account) @@ -46,6 +49,7 @@ def render_template(request, template_name, **kwargs): return flask_render_template( template_name, lockdown=lockdown, + accepting_dues=accepting_dues, is_active=is_active, is_alumni=is_alumni, is_eboard=is_eboard, diff --git a/frontend/javascript/modules/acceptDuesDatepicker.js b/frontend/javascript/modules/acceptDuesDatepicker.js new file mode 100644 index 00000000..b83d9905 --- /dev/null +++ b/frontend/javascript/modules/acceptDuesDatepicker.js @@ -0,0 +1,58 @@ +/* global $ */ +import "bootstrap-material-datetimepicker"; +import "whatwg-fetch"; +import FetchUtil from "../utils/fetchUtil"; +import Exception from "../exceptions/exception"; +import FetchException from "../exceptions/fetchException"; +import sweetAlert from "../../../node_modules/bootstrap-sweetalert/dev/sweetalert.es6.js"; // eslint-disable-line max-len + +export default class DatePicker { + constructor(input) { + this.input = input; + this.endpoint = '/manage/accept_dues_until'; + this.setting = input.dataset.setting; + this.render(); + } + + render() { + $(this.input).bootstrapMaterialDatePicker({ + weekStart: 0, + time: false + }); + + document.getElementsByClassName('dtp-btn-ok')[0].addEventListener('click', + () => { + this._updateSetting(); + }); + } + + _updateSetting() { + console.log("Update dues until: " + this.input.value); + let payload = {}; + payload[this.setting] = this.input.value; + + fetch(this.endpoint, { + method: 'PUT', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + credentials: 'same-origin', + body: JSON.stringify(payload) + }) + .then(FetchUtil.checkStatus) + .then(FetchUtil.parseJSON) + .then(response => { + if (!response.hasOwnProperty('success') || !response.success) { + sweetAlert("Uh oh...", "We're having trouble submitting this " + + "form right now. Please try again later.", "error"); + throw new Exception(FetchException.REQUEST_FAILED, response); + } + }) + .catch(error => { + sweetAlert("Uh oh...", "We're having trouble submitting this " + + "form right now. Please try again later.", "error"); + throw new Exception(FetchException.REQUEST_FAILED, error); + }); + } +} diff --git a/frontend/javascript/modules/becomeActive.js b/frontend/javascript/modules/becomeActive.js index b87067a2..200df96f 100644 --- a/frontend/javascript/modules/becomeActive.js +++ b/frontend/javascript/modules/becomeActive.js @@ -3,7 +3,6 @@ import FetchUtil from "../utils/fetchUtil"; export default class becomeActive { constructor(link) { this.link = link; - this.uid = this.link.dataset.uid; this.endpoint = '/manage/make_user_active'; this.render(); } @@ -15,11 +14,7 @@ export default class becomeActive { _delete(e) { e.preventDefault(); - let payload = { - uid: this.uid - }; - - FetchUtil.postWithWarning(this.endpoint, payload, { + FetchUtil.postWithWarning(this.endpoint, {}, { warningText: "Becoming an active member means that you will be charged" + " dues, which are $80 per semester.", successText: "You are now an active member." diff --git a/frontend/stylesheets/pages/_management.scss b/frontend/stylesheets/pages/_management.scss index 23e70bc1..ba5d2d07 100644 --- a/frontend/stylesheets/pages/_management.scss +++ b/frontend/stylesheets/pages/_management.scss @@ -12,6 +12,17 @@ margin: 30px 0; } +.btn-get-active { + float: right; + margin-top: -4px; + box-shadow: none; + padding: 3px 7px; +} + +.accept-dues-until { + margin-top: 10px; +} + .upload-title { padding-top: 20px; height: 55px; diff --git a/migrations/versions/d1a06ab54211_.py b/migrations/versions/d1a06ab54211_.py new file mode 100644 index 00000000..e0e3c41b --- /dev/null +++ b/migrations/versions/d1a06ab54211_.py @@ -0,0 +1,26 @@ +"""Add Accept Dues Until site setting + +Revision ID: d1a06ab54211 +Revises: 117567def844 +Create Date: 2017-07-21 17:09:37.540766 + +""" + +# revision identifiers, used by Alembic. +revision = 'd1a06ab54211' +down_revision = '117567def844' + +from alembic import op +import sqlalchemy as sa +from datetime import datetime + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('settings', sa.Column('accept_dues_until', sa.Date(), server_default=datetime.now().strftime("%Y-%m-%d"), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('settings', 'accept_dues_until') + # ### end Alembic commands ### From 0b4e6192041cefe810709f0b1190bfe06f2b7ca3 Mon Sep 17 00:00:00 2001 From: Ram Zallan Date: Tue, 25 Jul 2017 14:04:55 -0400 Subject: [PATCH 4/4] Add DUES_PER_SEMESTER config instead of hardcoding --- conditional/blueprints/member_management.py | 7 +++++-- config.sample.py | 3 +++ frontend/javascript/modules/becomeActive.js | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index dc1be6d0..e0da46ca 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -8,6 +8,8 @@ from flask import Blueprint, request, jsonify, abort, make_response +from conditional import app + from conditional.models.models import FreshmanAccount from conditional.models.models import FreshmanEvalData from conditional.models.models import FreshmanCommitteeAttendance @@ -642,10 +644,11 @@ def export_active_list(): will_coop = CurrentCoops.query.filter( CurrentCoops.date_created > start_of_year(), CurrentCoops.uid == member.uid).first() + dues_per_semester = app.config['DUES_PER_SEMESTER'] if will_coop: - dues = 80 + dues = dues_per_semester else: - dues = 160 + dues = 2 * dues_per_semester active_list.append([full_name, rit_username, dues]) csvw.writerows(active_list) diff --git a/config.sample.py b/config.sample.py index 49eaa16a..4fc12ba4 100644 --- a/config.sample.py +++ b/config.sample.py @@ -24,3 +24,6 @@ # Database config SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(os.path.join(os.getcwd(), "data.db")) ZOO_DATABASE_URI = 'mysql+pymysql://user:pass@host/database' + +# General config +DUES_PER_SEMESTER = 80 diff --git a/frontend/javascript/modules/becomeActive.js b/frontend/javascript/modules/becomeActive.js index 200df96f..4de6d1f0 100644 --- a/frontend/javascript/modules/becomeActive.js +++ b/frontend/javascript/modules/becomeActive.js @@ -16,7 +16,7 @@ export default class becomeActive { FetchUtil.postWithWarning(this.endpoint, {}, { warningText: "Becoming an active member means that you will be charged" + - " dues, which are $80 per semester.", + " dues, which are detailed in the CSH Constitution.", successText: "You are now an active member." }, () => { document.getElementById('becomeActive').remove();