Skip to content

Commit ad9e6bf

Browse files
authored
Merge pull request #68 from zedoax/develop
Major Performance Enhancements
2 parents 89de12c + ec62aa5 commit ad9e6bf

File tree

8 files changed

+347
-110
lines changed

8 files changed

+347
-110
lines changed

packet/member.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
from collections import namedtuple
2+
from logging import getLogger
3+
4+
from sqlalchemy import exc
5+
6+
from .models import db, REQUIRED_MISC_SIGNATURES
7+
from .packet import get_number_required, get_misc_signatures
8+
9+
LOGGER = getLogger(__name__)
10+
11+
12+
def current_packets(member, intro=False, onfloor=False):
13+
"""
14+
Get a list of currently open packets with the signed state of each packet.
15+
:param member: the member currently viewing all packets
16+
:param intro: true if current member is an intro member
17+
:param other: true if current member is off floor or alumni
18+
:return: <tuple> a list of packets that are currently open, and their attributes
19+
"""
20+
21+
# Tuple for compatibility with UI code. Should be refactored or deleted altogether later
22+
SPacket = namedtuple('spacket', ['rit_username', 'name', 'did_sign', 'total_signatures', 'required_signatures'])
23+
24+
packets = []
25+
required = get_number_required()
26+
27+
if intro and onfloor:
28+
required -= 1
29+
30+
signed_packets = get_signed_packets(member, intro, onfloor)
31+
misc_signatures = get_misc_signatures()
32+
33+
try:
34+
for pkt in query_packets_with_signed():
35+
signed = signed_packets.get(pkt.username)
36+
misc = misc_signatures.get(pkt.username)
37+
if signed is None:
38+
signed = False
39+
if misc is None:
40+
misc = 0
41+
if misc > REQUIRED_MISC_SIGNATURES:
42+
misc = REQUIRED_MISC_SIGNATURES
43+
packets.append(SPacket(pkt.username, pkt.name, signed, pkt.received + misc, required))
44+
45+
except exc.SQLAlchemyError as e:
46+
LOGGER.error(e)
47+
raise e
48+
49+
return packets
50+
51+
52+
def get_signed_packets(member, intro=False, onfloor=False):
53+
"""
54+
Get a list of all packets that a member has signed
55+
:param member: member retrieving prior packet signatures
56+
:param intro: is the member an intro member?
57+
:param onfloor: is the member on floor?
58+
:return: <dict> usernames mapped to signed status
59+
"""
60+
signed_packets = {}
61+
62+
try:
63+
if intro and onfloor:
64+
for signature in query_signed_intromember(member):
65+
signed_packets[signature.username] = signature.signed
66+
67+
if not intro:
68+
if onfloor:
69+
for signature in query_signed_upperclassman(member):
70+
signed_packets[signature.username] = signature.signed
71+
72+
else:
73+
for signature in query_signed_alumni(member):
74+
signed_packets[signature.username] = bool(signature.signed)
75+
76+
except exc.SQLAlchemyError as e:
77+
LOGGER.error(e)
78+
raise e
79+
80+
return signed_packets
81+
82+
83+
def query_packets_with_signed():
84+
"""
85+
Query the database and return a list of currently open packets and the number of signatures they currently have
86+
:return: a list of results: intro members with open packets, their name, username, and number of signatures received
87+
"""
88+
try:
89+
return db.engine.execute("""
90+
SELECT packets.username AS username, packets.name AS name, coalesce(packets.sigs_recvd, 0) AS received
91+
FROM ( ( SELECT freshman.rit_username
92+
AS username, freshman.name AS name, packet.id AS id, packet.start AS start, packet.end AS end
93+
FROM freshman INNER JOIN packet ON freshman.rit_username = packet.freshman_username) AS a
94+
LEFT JOIN ( SELECT totals.id AS id, coalesce(sum(totals.signed), 0) AS sigs_recvd
95+
FROM ( SELECT packet.id AS id, coalesce(count(signature_fresh.signed), 0) AS signed
96+
FROM packet FULL OUTER JOIN signature_fresh ON signature_fresh.packet_id = packet.id
97+
WHERE signature_fresh.signed = TRUE AND packet.start < now() AND now() < packet.end
98+
GROUP BY packet.id
99+
UNION SELECT packet.id AS id, coalesce(count(signature_upper.signed), 0) AS signed FROM packet
100+
FULL OUTER JOIN signature_upper ON signature_upper.packet_id = packet.id
101+
WHERE signature_upper.signed = TRUE AND packet.start < now() AND now() < packet.end
102+
GROUP BY packet.id ) totals GROUP BY totals.id ) AS b ON a.id = b.id ) AS packets
103+
WHERE packets.start < now() AND now() < packets.end;
104+
""")
105+
106+
except exc.SQLAlchemyError:
107+
raise exc.SQLAlchemyError("Error: Failed to get open packets with signatures received from database")
108+
109+
110+
def query_signed_intromember(member):
111+
"""
112+
Query the database and return the list of packets signed by the given intro member
113+
:param member: the user making the query
114+
:return: list of results matching the query
115+
"""
116+
try:
117+
return db.engine.execute("""
118+
SELECT DISTINCT packet.freshman_username AS username, signature_fresh.signed AS signed FROM packet
119+
INNER JOIN signature_fresh ON packet.id = signature_fresh.packet_id
120+
WHERE signature_fresh.freshman_username = """ + member + ";")
121+
122+
except exc.SQLAlchemyError:
123+
raise exc.SQLAlchemyError("Error: Failed to get intromember's signatures from database")
124+
125+
126+
def query_signed_upperclassman(member):
127+
"""
128+
Query the database and return the list of packets signed by the given upperclassman
129+
:param member: the user making the query
130+
:return: list of results matching the query
131+
"""
132+
try:
133+
return db.engine.execute("""
134+
SELECT DISTINCT packet.freshman_username AS username, signature_upper.signed AS signed FROM packet
135+
INNER JOIN signature_upper ON packet.id = signature_upper.packet_id
136+
WHERE signature_upper.member = '""" + member + "';")
137+
138+
except exc.SQLAlchemyError:
139+
raise exc.SQLAlchemyError("Error: Failed to get upperclassman's signatures from database")
140+
141+
142+
def query_signed_alumni(member):
143+
"""
144+
Query the database and return the list of packets signed by the given alumni/off-floor
145+
:param member: the user making the query
146+
:return: list of results matching the query
147+
"""
148+
try:
149+
return db.engine.execute("""
150+
SELECT DISTINCT packet.freshman_username AS username, signature_misc.member AS signed
151+
FROM packet LEFT OUTER JOIN signature_misc ON packet.id = signature_misc.packet_id
152+
WHERE signature_misc.member = '""" + member + "' OR signature_misc.member ISNULL;")
153+
154+
except exc.SQLAlchemyError:
155+
raise exc.SQLAlchemyError("Error: Failed to get alumni's signatures from database")

packet/models.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from datetime import datetime
66
from functools import lru_cache
77

8-
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, Boolean
8+
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, Boolean, and_, or_
99
from sqlalchemy.orm import relationship
1010

1111
from . import db
@@ -50,25 +50,50 @@ def is_open(self):
5050
return self.start < datetime.now() < self.end
5151

5252
@lru_cache(maxsize=1024)
53-
def signatures_required(self):
53+
def signatures_required(self, total=False):
54+
if total:
55+
return len(self.upper_signatures) + len(self.fresh_signatures) + REQUIRED_MISC_SIGNATURES
5456
eboard = UpperSignature.query.with_parent(self).filter_by(eboard=True).count()
5557
return {'eboard': eboard,
5658
'upperclassmen': len(self.upper_signatures) - eboard,
5759
'freshmen': len(self.fresh_signatures),
58-
'misc': REQUIRED_MISC_SIGNATURES}
60+
'miscellaneous': REQUIRED_MISC_SIGNATURES}
5961

60-
def signatures_received(self):
62+
def signatures_received(self, total=False):
6163
"""
6264
Result capped so it will never be greater than that of signatures_required()
6365
"""
64-
eboard_count = UpperSignature.query.with_parent(self).filter_by(signed=True, eboard=True).count()
65-
upper_count = UpperSignature.query.with_parent(self).filter_by(signed=True, eboard=False).count()
66-
fresh_count = FreshSignature.query.with_parent(self).filter_by(signed=True).count()
6766
misc_count = len(self.misc_signatures)
6867

6968
if misc_count > REQUIRED_MISC_SIGNATURES:
7069
misc_count = REQUIRED_MISC_SIGNATURES
7170

71+
if total:
72+
return db.session.query(Packet.freshman_username) \
73+
.select_from(Packet).outerjoin(UpperSignature).outerjoin(FreshSignature) \
74+
.filter(or_(and_(Packet.freshman_username == self.freshman_username, UpperSignature.signed),
75+
and_(Packet.freshman_username == self.freshman_username, FreshSignature.signed))) \
76+
.distinct().count() + misc_count
77+
78+
eboard_count = db.session.query(UpperSignature.member) \
79+
.select_from(Packet).join(UpperSignature) \
80+
.filter(Packet.freshman_username == self.freshman_username,
81+
UpperSignature.signed, UpperSignature.eboard) \
82+
.distinct().count()
83+
84+
upper_count = db.session.query(UpperSignature.member) \
85+
.select_from(Packet).join(UpperSignature) \
86+
.filter(Packet.freshman_username == self.freshman_username,
87+
UpperSignature.signed,
88+
UpperSignature.eboard.isnot(True)) \
89+
.distinct().count()
90+
91+
fresh_count = db.session.query(FreshSignature.freshman_username) \
92+
.select_from(Packet).join(FreshSignature) \
93+
.filter(Packet.freshman_username == self.freshman_username,
94+
FreshSignature.signed) \
95+
.distinct().count()
96+
7297
return {'eboard': eboard_count,
7398
'upperclassmen': upper_count,
7499
'freshmen': fresh_count,

packet/packet.py

Lines changed: 98 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import copy
1+
from datetime import datetime
22
from functools import lru_cache
33

4+
from sqlalchemy import exc
5+
46
from packet.ldap import ldap_get_member, ldap_is_intromember
5-
from .models import Freshman, UpperSignature, FreshSignature, MiscSignature, db
7+
from .models import Freshman, UpperSignature, FreshSignature, MiscSignature, db, Packet
68

79

810
def sign(signer_username, freshman_username):
@@ -34,20 +36,81 @@ def sign(signer_username, freshman_username):
3436
return True
3537

3638

39+
def get_essays(freshman_username):
40+
packet = Freshman.query.filter_by(rit_username=freshman_username).first().current_packet()
41+
return {'eboard': packet.info_eboard,
42+
'events': packet.info_events,
43+
'achieve': packet.info_achieve}
44+
45+
46+
def set_essays(freshman_username, eboard=None, events=None, achieve=None):
47+
packet = Freshman.query.filter_by(rit_username=freshman_username).first().current_packet()
48+
if eboard is not None:
49+
packet.info_eboard = eboard
50+
if events is not None:
51+
packet.info_events = events
52+
if achieve is not None:
53+
packet.info_achieve = achieve
54+
try:
55+
db.session.commit()
56+
except exc.SQLAlchemyError:
57+
return False
58+
return True
59+
60+
3761
@lru_cache(maxsize=2048)
3862
def get_signatures(freshman_username):
39-
packet = get_current_packet(freshman_username)
40-
eboard = UpperSignature.query.filter_by(packet_id=packet.id, eboard=True).order_by(UpperSignature.signed.desc())
41-
upper_signatures = UpperSignature.query.filter_by(packet_id=packet.id, eboard=False).order_by(
42-
UpperSignature.signed.desc())
43-
fresh_signatures = FreshSignature.query.filter_by(packet_id=packet.id).order_by(FreshSignature.signed.desc())
44-
misc_signatures = MiscSignature.query.filter_by(packet_id=packet.id)
63+
"""
64+
Gets a list of all signatures for the given member
65+
:param freshman_username: the freshman to get the signatures in their most recent packet
66+
:return: <dict><list> list of signatures for the different categories
67+
"""
68+
packet = Freshman.query.filter_by(rit_username=freshman_username).first().current_packet()
69+
70+
eboard = db.session.query(UpperSignature.member, UpperSignature.signed, Freshman.rit_username) \
71+
.select_from(UpperSignature).join(Packet).join(Freshman) \
72+
.filter(UpperSignature.packet_id == packet.id, UpperSignature.eboard.is_(True)) \
73+
.order_by(UpperSignature.signed.desc()) \
74+
.distinct().all()
75+
76+
upper_signatures = db.session.query(UpperSignature.member, UpperSignature.signed, Freshman.rit_username) \
77+
.select_from(UpperSignature).join(Packet).join(Freshman) \
78+
.filter(UpperSignature.packet_id == packet.id, UpperSignature.eboard.is_(False))\
79+
.order_by(UpperSignature.signed.desc())\
80+
.distinct().all()
81+
fresh_signatures = \
82+
db.session.query(FreshSignature.freshman_username, FreshSignature.signed, Freshman.rit_username, Freshman.name) \
83+
.select_from(Packet).join(FreshSignature).join(Freshman) \
84+
.filter(FreshSignature.packet_id == packet.id) \
85+
.order_by(FreshSignature.signed.desc()) \
86+
.distinct().all()
87+
88+
misc_signatures = db.session.query(MiscSignature.member, Freshman.rit_username)\
89+
.select_from(MiscSignature).join(Packet).join(Freshman) \
90+
.filter(MiscSignature.packet_id == packet.id) \
91+
.distinct().all()
92+
4593
return {'eboard': eboard,
4694
'upperclassmen': upper_signatures,
4795
'freshmen': fresh_signatures,
4896
'misc': misc_signatures}
4997

5098

99+
def get_misc_signatures():
100+
packet_misc_sigs = {}
101+
try:
102+
result = db.engine.execute("""
103+
SELECT packet.freshman_username AS username, count(signature_misc.member) AS signatures FROM packet
104+
RIGHT OUTER JOIN signature_misc ON packet.id = signature_misc.packet_id
105+
GROUP BY packet.freshman_username;
106+
""")
107+
for packet in result:
108+
packet_misc_sigs[packet.username] = packet.signatures
109+
except exc.SQLAlchemyError:
110+
raise exc.SQLAlchemyError("Error: Unable to query miscellaneous signatures from database")
111+
return packet_misc_sigs
112+
113+
51114
@lru_cache(maxsize=2048)
52115
def valid_signature(signer_username, freshman_username):
53116
if signer_username == freshman_username:
@@ -75,24 +138,39 @@ def get_current_packet(freshman_username):
75138

76139

77140
@lru_cache(maxsize=2048)
78-
def get_number_signed(freshman_username):
79-
return get_current_packet(freshman_username).signatures_received()
141+
def get_number_signed(freshman_username, separated=False):
142+
"""
143+
Gets the raw number of signatures for the user
144+
:param freshman_username: The user to get signature numbers for
145+
:param separated: A boolean indicating whether to return the results as separated
146+
:return: <Packet> list of results that are in the form of Packet database objects
147+
"""
148+
return db.session.query(Packet) \
149+
.filter(Packet.freshman_username == freshman_username,
150+
Packet.start < datetime.now(), Packet.end > datetime.now()) \
151+
.first().signatures_received(not separated)
80152

81153

82154
@lru_cache(maxsize=2048)
83-
def get_number_required(freshman_username):
84-
return get_current_packet(freshman_username).signatures_required()
155+
def get_number_required(separated=False):
156+
"""
157+
Get the number of required signatures for Packet (not counting on/off-floor status)
158+
:param separated: whether or not to separate those by category
159+
:return: a map or an integer of total signatures required
160+
"""
161+
return db.session.query(Packet) \
162+
.filter(Packet.start < datetime.now(), Packet.end > datetime.now()).first().signatures_required(not separated)
85163

86164

87165
@lru_cache(maxsize=2048)
88-
def get_upperclassmen_percent(uid):
89-
upperclassmen_required = copy.deepcopy(get_number_required(uid))
90-
del upperclassmen_required['freshmen']
91-
upperclassmen_required = sum(upperclassmen_required.values())
92-
93-
upperclassmen_signature = copy.deepcopy(get_number_signed(uid))
94-
del upperclassmen_signature['freshmen']
95-
upperclassmen_signature = sum(upperclassmen_signature.values())
166+
def get_upperclassmen_percent(username, onfloor=False):
167+
required = get_number_required(True)
168+
upperclassmen_required = required['upperclassmen'] + required['eboard'] + required['miscellaneous']
169+
if onfloor:
170+
upperclassmen_required -= 1
171+
172+
signatures = get_number_signed(username, True)
173+
upperclassmen_signature = signatures['upperclassmen'] + signatures['eboard'] + signatures['miscellaneous']
96174

97175
return upperclassmen_signature / upperclassmen_required * 100
98176

packet/routes/api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ def sign(member_username, packet_username, info):
1616
if not sign_packet(member_username, packet_username):
1717
return "Error: Signature not valid. Reason: Unknown"
1818
return "Success: Signed Packet: " + packet_username
19+

0 commit comments

Comments
 (0)