-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathmodels.py
More file actions
227 lines (182 loc) · 6.78 KB
/
models.py
File metadata and controls
227 lines (182 loc) · 6.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import time
import re
from datetime import datetime
from marshmallow import Schema, fields, validate, ValidationError, EXCLUDE
from marshmallow.decorators import validates, pre_load, post_load
DAYS_PATTERN = f"^{'(M|T|W|Th|F|S|U)?'*7}$"
class ClassDataSchema(Schema):
"""
Class Strings
"""
# 5-digit Course Reference Number (ex. 25668)
CRN = fields.Int(required=True)
# Raw course string (ex. "MATH F001D.01Z")
raw_course = fields.Str(required=True)
# Department (ex. "CIS" or "MATH")
dept = fields.Str(required=True)
# Course (ex. "1A" or "31D")
course = fields.Str(required=True)
# Class section (ex. "01Z")
section = fields.Str()
# Class variant (ex. "Z")
variant = fields.Str(validate=validate.OneOf(['', 'W', 'Z', 'Y', 'H']))
"""
Course Info
"""
# Course title
title = fields.Str(required=True)
# Class units
units = fields.Float(required=True, min=0)
"""
Class Dates
"""
# Start date
start = fields.Str(required=True)
# End date
end = fields.Str(required=True)
"""
Seat info
"""
# Class status (Open, Waitlist, Full)
# status = fields.Str(required=True, validate=validate.OneOf(['open', 'waitlist', 'full', 'unknown']))
status = fields.Str(validate=validate.OneOf(['open', 'waitlist', 'full', 'unknown']))
# Number of open seats
# seats = fields.Int(required=True, min=0)
seats = fields.Int(min=0)
# Number of open waitlist seats
# wait_seats = fields.Int(required=True, min=0)
wait_seats = fields.Int(min=0)
# Waitlist capacity (total # of waitlist seats)
# wait_cap = fields.Int(required=True, min=0)
wait_cap = fields.Int(min=0)
class Meta:
ordered = True
unknown = EXCLUDE
@validates('start')
def validate_start(self, date_str):
self.validate_date(date_str)
@validates('end')
def validate_end(self, date_str):
self.validate_date(date_str)
def validate_date(self, date_str):
"""
Validate the date string format
"""
try:
if not date_str == 'TBA':
datetime.strptime(date_str, '%m/%d/%Y')
except ValueError:
raise ValidationError('Date must be in the format %m/%d/%Y or be "TBA".')
@post_load
def fix(self, data, **kwargs):
if not data.get('status') and data.get('seats') != None and data.get('wait_seats') != None:
data['status'] = (
'open' if data['seats'] > 0 else
'waitlist' if data['wait_seats'] > 0 else
'full'
)
return data
def clean_instructor_name(name):
# Replace ', ' with '', '(P)' with '', ' ' (n spaces) with ' ' (one space)
return re.sub(r'\s+', ' ', re.sub(r'(?:, )|(?:\(\w?\))', '', name)).strip()
class ClassTimeSchema(Schema):
type = fields.Str()
days = fields.Str(required=True)
# time = fields.Str(required=True)
start_time = fields.Str(required=True)
end_time = fields.Str(required=True)
instructor = fields.List(fields.Str(), required=True)
location = fields.Str(required=True)
room = fields.Str()
campus = fields.Str()
# campus = fields.Str(required=True, validate=validate.OneOf(
# ['FH', 'FC', 'FO', 'DA', 'DO', ''] + ['FM'] # 'FM' is only found in archived data
# ))
class Meta:
ordered = True
unknown = EXCLUDE
@pre_load
def split_time(self, data, **kwargs):
if 'time' in data and not ('start_time' in data or 'end_time' in data):
combo_time = data['time']
times = ['TBA', 'TBA'] if combo_time == 'TBA' else combo_time.split('-')
if len(times) != 2:
raise ValidationError(
f"The time string '{combo_time}' has to be 'TBA' or be two times separated by a '-'",
field_name='start_time'
)
data['start_time'] = times[0].strip()
data['end_time'] = times[1].strip()
for key in ['start_time', 'end_time']:
time_str = data[key]
# Validate the time string format
if time_str == 'TBA':
continue
try:
parsed_time = time.strptime(time_str, '%I:%M %p')
except ValueError:
raise ValidationError('Time must be in the format %I:%M %p.', field_name=key)
data[key] = time.strftime('%I:%M %p', parsed_time)
return data
@pre_load
def fix_days(self, data, **kwargs):
if not data['days']:
replaced = False
for key in ['start_time', 'time']:
if key in data and data[key] == 'TBA':
data['days'] = 'TBA'
replaced = True
break
# TODO: "unknown" instead of "TBA"
if not replaced:
data['days'] = 'TBA'
return data
@pre_load
def split_instructor(self, data, **kwargs):
instructors = data.get('instructor')
if instructors and isinstance(instructors, str):
data['instructor'] = [clean_instructor_name(instructor.strip()) for instructor in instructors.split(',')]
return data
@validates('days')
def validate_days(self, days_str):
if days_str != 'TBA' and not re.match(DAYS_PATTERN, days_str):
raise ValidationError('Days string is not "TBA" and validation regex does not match.')
# @validates('start_time')
# def validate_start_time(self, date_str):
# self.validate_time(date_str)
# @validates('end_time')
# def validate_end_time(self, date_str):
# self.validate_time(date_str)
# def validate_time(self, time_str):
# """
# Validate the time string format
# """
# if time_str == 'TBA':
# return
# try:
# time.strptime(time_str, '%I:%M %p')
# except ValueError:
# raise ValidationError('Time must be in the format %I:%M %p.')
class InterimClassDataSchema(ClassDataSchema, ClassTimeSchema):
pass
class SeatInfoSchema(Schema):
# 5-digit Course Reference Number (ex. 25668)
CRN = fields.Int(required=True)
"""
Seat info
"""
# Class status (Open, Waitlist, Full)
status = fields.Str(required=True, validate=validate.OneOf(['open', 'waitlist', 'full', 'unknown']))
# Number of open seats
seats = fields.Int(required=True, min=0)
# Number of open waitlist seats
wait_seats = fields.Int(required=True, min=0)
# Waitlist capacity (total # of waitlist seats)
wait_cap = fields.Int(required=True, min=0)
class Meta:
ordered = True
unknown = EXCLUDE
classDataSchema = ClassDataSchema()
classTimeSchema = ClassTimeSchema()
interimClassDataSchema = InterimClassDataSchema()
seatInfoSchema = SeatInfoSchema()