Skip to content

Commit 57d4402

Browse files
authored
Merge pull request #147 from RamZallan/develop
Allow Members to Opt-In to Active Status
2 parents 2d06e8d + 0b4e619 commit 57d4402

File tree

11 files changed

+238
-3
lines changed

11 files changed

+238
-3
lines changed

conditional/blueprints/dashboard.py

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from conditional.util.ldap import ldap_is_onfloor
66
from conditional.util.ldap import ldap_is_active
77
from conditional.util.ldap import ldap_is_intromember
8+
from conditional.util.ldap import ldap_is_current_student
89
from conditional.util.ldap import ldap_get_member
910
from conditional.util.ldap import ldap_get_active_members
1011

@@ -44,6 +45,8 @@ def display_dashboard():
4445
data['active'] = ldap_is_active(member)
4546
data['onfloor'] = ldap_is_onfloor(member)
4647
data['voting'] = bool(member.uid in can_vote)
48+
data['student'] = ldap_is_current_student(member)
49+
4750

4851
data['voting_count'] = {"Voting Members": len(can_vote),
4952
"Active Members": len(ldap_get_active_members())}

conditional/blueprints/member_management.py

+76-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import csv
22
import io
3+
import re
34

45
from datetime import datetime
56

67
import structlog
78

8-
from flask import Blueprint, request, jsonify, abort
9+
from flask import Blueprint, request, jsonify, abort, make_response
10+
11+
from conditional import app
912

1013
from conditional.models.models import FreshmanAccount
1114
from conditional.models.models import FreshmanEvalData
@@ -28,6 +31,7 @@
2831
from conditional.util.ldap import ldap_is_financial_director
2932
from conditional.util.ldap import ldap_is_active
3033
from conditional.util.ldap import ldap_is_onfloor
34+
from conditional.util.ldap import ldap_is_current_student
3135
from conditional.util.ldap import ldap_set_roomnumber
3236
from conditional.util.ldap import ldap_set_active
3337
from conditional.util.ldap import ldap_set_inactive
@@ -87,9 +91,11 @@ def display_member_management():
8791
if settings:
8892
lockdown = settings.site_lockdown
8993
intro_form = settings.intro_form_active
94+
accept_dues_until = settings.accept_dues_until
9095
else:
9196
lockdown = False
9297
intro_form = False
98+
accept_dues_until = datetime.now()
9399

94100
return render_template(request, "member_management.html",
95101
username=username,
@@ -101,6 +107,7 @@ def display_member_management():
101107
freshmen=freshmen_list,
102108
co_op=co_op_list,
103109
site_lockdown=lockdown,
110+
accept_dues_until=accept_dues_until,
104111
intro_form=intro_form)
105112

106113

@@ -135,6 +142,31 @@ def member_management_eval():
135142
return jsonify({"success": True}), 200
136143

137144

145+
@member_management_bp.route('/manage/accept_dues_until', methods=['PUT'])
146+
def member_management_financial():
147+
log = logger.new(request=request)
148+
149+
username = request.headers.get('x-webauth-user')
150+
account = ldap_get_member(username)
151+
152+
if not ldap_is_financial_director(account):
153+
return "must be financial director", 403
154+
155+
post_data = request.get_json()
156+
157+
if 'acceptDuesUntil' in post_data:
158+
date = datetime.strptime(post_data['acceptDuesUntil'], "%Y-%m-%d")
159+
log.info('Changed Dues Accepted Until: {}'.format(date))
160+
EvalSettings.query.update(
161+
{
162+
'accept_dues_until': date
163+
})
164+
165+
db.session.flush()
166+
db.session.commit()
167+
return jsonify({"success": True}), 200
168+
169+
138170
@member_management_bp.route('/manage/user', methods=['POST'])
139171
def member_management_adduser():
140172
log = logger.new(request=request)
@@ -491,6 +523,23 @@ def member_management_upgrade_user():
491523
return jsonify({"success": True}), 200
492524

493525

526+
@member_management_bp.route('/manage/make_user_active', methods=['POST'])
527+
def member_management_make_user_active():
528+
log = logger.new(request=request)
529+
530+
uid = request.headers.get('x-webauth-user')
531+
account = ldap_get_member(uid)
532+
533+
if not ldap_is_current_student(account) or ldap_is_active(account):
534+
return "must be current student and not active", 403
535+
536+
ldap_set_active(account)
537+
log.info("Make user {} active".format(uid))
538+
539+
clear_members_cache()
540+
return jsonify({"success": True}), 200
541+
542+
494543
@member_management_bp.route('/manage/intro_project', methods=['GET'])
495544
def introductory_project():
496545
log = logger.new(request=request)
@@ -583,6 +632,32 @@ def clear_active_members():
583632
return jsonify({"success": True}), 200
584633

585634

635+
@member_management_bp.route('/manage/export_active_list', methods=['GET'])
636+
def export_active_list():
637+
sio = io.StringIO()
638+
csvw = csv.writer(sio)
639+
640+
active_list = [["Full Name", "RIT Username", "Amount to Charge"]]
641+
for member in ldap_get_active_members():
642+
full_name = member.cn
643+
rit_username = re.search(".*uid=(\\w*)", member.ritDn).group(1)
644+
will_coop = CurrentCoops.query.filter(
645+
CurrentCoops.date_created > start_of_year(),
646+
CurrentCoops.uid == member.uid).first()
647+
dues_per_semester = app.config['DUES_PER_SEMESTER']
648+
if will_coop:
649+
dues = dues_per_semester
650+
else:
651+
dues = 2 * dues_per_semester
652+
active_list.append([full_name, rit_username, dues])
653+
654+
csvw.writerows(active_list)
655+
output = make_response(sio.getvalue())
656+
output.headers["Content-Disposition"] = "attachment; filename=csh_active_list.csv"
657+
output.headers["Content-type"] = "text/csv"
658+
return output
659+
660+
586661
@member_management_bp.route('/manage/current/<uid>', methods=['POST', 'DELETE'])
587662
def remove_current_student(uid):
588663
log = logger.new(request=request)

conditional/models/models.py

+2
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,13 @@ class EvalSettings(db.Model):
243243
housing_form_active = Column(Boolean)
244244
intro_form_active = Column(Boolean)
245245
site_lockdown = Column(Boolean)
246+
accept_dues_until = Column(Date)
246247

247248
def __init__(self):
248249
self.housing_form_active = True
249250
self.intro_form_active = True
250251
self.site_lockdown = False
252+
self.accept_dues_until = datetime.now()
251253

252254

253255
class SpringEval(db.Model):

conditional/templates/dashboard.html

+15
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,22 @@ <h3 class="panel-title">Major Projects</h3>
251251
</div>
252252
{% endif %}
253253

254+
{% if accepting_dues and student and not active %}
255+
<div class="panel panel-info" id="becomeActive">
256+
<div class="panel-heading">
257+
<h3 class="panel-title">Become Active</h3>
258+
</div>
259+
<div class="panel-body">
260+
Hey there, you're eligible to become an active member! Click the button below if you'd like to become active and pay dues.
261+
</div>
262+
<div class="panel-footer text-right">
263+
<a href="#" data-module="becomeActive" class="btn btn-sm btn-default">Become Active</a>
264+
</div>
265+
</div>
266+
{% endif %}
267+
254268
</div>
269+
255270
<div class="col-lg-6 col-md-6">
256271
{% if housing %}
257272
<div class="panel panel-default">

conditional/templates/member_management.html

+14-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ <h3 class="panel-title">Administration</h3>
1010
</div>
1111
<div class="panel-body">
1212
<div class="container-fluid">
13-
<div class="col-xs-6 col-sm-2{% if not is_eval_director %} col-sm-offset-2{% endif %}">
13+
<div class="col-xs-6 col-sm-2">
1414
<div class="stat-number">{{num_current}}</div>
1515
<div class="stat-title">Current Students</div>
1616
</div>
@@ -37,6 +37,11 @@ <h3 class="panel-title">Administration</h3>
3737
<div class="col-xs-6 col-sm-2 align-center">
3838
<a href="/manage/new" class="btn btn-danger btn-sm btn-new-year"><span class="glyphicon glyphicon-repeat"></span> New Year</a>
3939
</div>
40+
{% else %}
41+
<div class="col-xs-6 col-sm-2 align-center">
42+
<label for="acceptDuesUntil" class="control-label accept-dues-until">Accept Dues Until</label>
43+
<input type="text" name="acceptDuesUntil" class="form-control" value="{{ accept_dues_until }}" data-module="acceptDuesDatepicker" data-setting="acceptDuesUntil" />
44+
</div>
4045
{% endif %}
4146
</div>
4247
</div>
@@ -166,7 +171,14 @@ <h3 class="panel-title">Freshmen Management</h3>
166171

167172
<div class="panel panel-default">
168173
<div class="panel-heading">
169-
<h3 class="panel-title">Member Management</h3>
174+
<h3 class="panel-title">
175+
Member Management
176+
<a href="/manage/export_active_list">
177+
<button type="button" class="btn btn-primary btn-sm btn-conditional pull-right">
178+
<span class="glyphicon glyphicon-save"></span> Export Active List
179+
</button>
180+
</a>
181+
</h3>
170182
</div>
171183
<div class="panel-body table-fill">
172184
<div class="table-responsive">

conditional/util/flask.py

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from datetime import date
2+
13
from flask import render_template as flask_render_template
24
from conditional.models.models import EvalSettings
35

@@ -27,6 +29,7 @@ def render_template(request, template_name, **kwargs):
2729
db.session.commit()
2830
account = ldap_get_member(user_name)
2931
lockdown = EvalSettings.query.first().site_lockdown
32+
accepting_dues = EvalSettings.query.first().accept_dues_until > date.today()
3033
is_active = ldap_is_active(account)
3134
is_alumni = ldap_is_alumni(account)
3235
is_eboard = ldap_is_eboard(account)
@@ -46,6 +49,7 @@ def render_template(request, template_name, **kwargs):
4649
return flask_render_template(
4750
template_name,
4851
lockdown=lockdown,
52+
accepting_dues=accepting_dues,
4953
is_active=is_active,
5054
is_alumni=is_alumni,
5155
is_eboard=is_eboard,

config.sample.py

+3
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@
2424
# Database config
2525
SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(os.path.join(os.getcwd(), "data.db"))
2626
ZOO_DATABASE_URI = 'mysql+pymysql://user:pass@host/database'
27+
28+
# General config
29+
DUES_PER_SEMESTER = 80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* global $ */
2+
import "bootstrap-material-datetimepicker";
3+
import "whatwg-fetch";
4+
import FetchUtil from "../utils/fetchUtil";
5+
import Exception from "../exceptions/exception";
6+
import FetchException from "../exceptions/fetchException";
7+
import sweetAlert from "../../../node_modules/bootstrap-sweetalert/dev/sweetalert.es6.js"; // eslint-disable-line max-len
8+
9+
export default class DatePicker {
10+
constructor(input) {
11+
this.input = input;
12+
this.endpoint = '/manage/accept_dues_until';
13+
this.setting = input.dataset.setting;
14+
this.render();
15+
}
16+
17+
render() {
18+
$(this.input).bootstrapMaterialDatePicker({
19+
weekStart: 0,
20+
time: false
21+
});
22+
23+
document.getElementsByClassName('dtp-btn-ok')[0].addEventListener('click',
24+
() => {
25+
this._updateSetting();
26+
});
27+
}
28+
29+
_updateSetting() {
30+
console.log("Update dues until: " + this.input.value);
31+
let payload = {};
32+
payload[this.setting] = this.input.value;
33+
34+
fetch(this.endpoint, {
35+
method: 'PUT',
36+
headers: {
37+
'Accept': 'application/json',
38+
'Content-Type': 'application/json'
39+
},
40+
credentials: 'same-origin',
41+
body: JSON.stringify(payload)
42+
})
43+
.then(FetchUtil.checkStatus)
44+
.then(FetchUtil.parseJSON)
45+
.then(response => {
46+
if (!response.hasOwnProperty('success') || !response.success) {
47+
sweetAlert("Uh oh...", "We're having trouble submitting this " +
48+
"form right now. Please try again later.", "error");
49+
throw new Exception(FetchException.REQUEST_FAILED, response);
50+
}
51+
})
52+
.catch(error => {
53+
sweetAlert("Uh oh...", "We're having trouble submitting this " +
54+
"form right now. Please try again later.", "error");
55+
throw new Exception(FetchException.REQUEST_FAILED, error);
56+
});
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import FetchUtil from "../utils/fetchUtil";
2+
3+
export default class becomeActive {
4+
constructor(link) {
5+
this.link = link;
6+
this.endpoint = '/manage/make_user_active';
7+
this.render();
8+
}
9+
10+
render() {
11+
this.link.addEventListener('click', e => this._delete(e));
12+
}
13+
14+
_delete(e) {
15+
e.preventDefault();
16+
17+
FetchUtil.postWithWarning(this.endpoint, {}, {
18+
warningText: "Becoming an active member means that you will be charged" +
19+
" dues, which are detailed in the CSH Constitution.",
20+
successText: "You are now an active member."
21+
}, () => {
22+
document.getElementById('becomeActive').remove();
23+
});
24+
}
25+
}
26+

frontend/stylesheets/pages/_management.scss

+11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
margin: 30px 0;
1313
}
1414

15+
.btn-get-active {
16+
float: right;
17+
margin-top: -4px;
18+
box-shadow: none;
19+
padding: 3px 7px;
20+
}
21+
22+
.accept-dues-until {
23+
margin-top: 10px;
24+
}
25+
1526
.upload-title {
1627
padding-top: 20px;
1728
height: 55px;

migrations/versions/d1a06ab54211_.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Add Accept Dues Until site setting
2+
3+
Revision ID: d1a06ab54211
4+
Revises: 117567def844
5+
Create Date: 2017-07-21 17:09:37.540766
6+
7+
"""
8+
9+
# revision identifiers, used by Alembic.
10+
revision = 'd1a06ab54211'
11+
down_revision = '117567def844'
12+
13+
from alembic import op
14+
import sqlalchemy as sa
15+
from datetime import datetime
16+
17+
def upgrade():
18+
# ### commands auto generated by Alembic - please adjust! ###
19+
op.add_column('settings', sa.Column('accept_dues_until', sa.Date(), server_default=datetime.now().strftime("%Y-%m-%d"), nullable=True))
20+
# ### end Alembic commands ###
21+
22+
23+
def downgrade():
24+
# ### commands auto generated by Alembic - please adjust! ###
25+
op.drop_column('settings', 'accept_dues_until')
26+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)