-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.py
309 lines (240 loc) · 10.3 KB
/
app.py
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
from flask import Flask, redirect, url_for, request, make_response
import asyncio
from functools import wraps
import uuid
import toml
import csv
from common.providers import sendGridProvider, send_email
from common.utilities import request_variables
from common.decorators import templated
from models import Person, Organization
from functions import get_fields, map_historic_data_fields
app = Flask(__name__)
# load the config file from the TOML formatted file (not checked into repository)
app.config.from_file('config.toml', toml.load)
app.config['NAVELEMENTS'] = {
'home': {
'nav': 'home',
'url': '',
'label': 'Home'
},
'signup': {
'nav': 'signup',
'url': '/users/add',
'label': 'Sign up'
},
'faq': {
'nav': 'faq',
'url': '/faq',
'label': 'FAQ'
}
}
app.config['NAVSET'] = {
'holding': [],
'normal': ['home','signup','faq']
}
@app.get('/')
@templated('web/home')
def home_handler():
"""
This function outputs the homepage
"""
return {}
@app.get('/faq/')
@app.get('/faq')
@templated('web/faq')
def faq_handler():
"""
This function outputs the FAQ page
"""
return {}
@app.get('/schema')
def schema_handler():
"""
This function outputs the schema used for the application
"""
person = Person({'name':'blank'})
return person.as_schema()
@app.get('/users/add/')
@app.get('/users/add')
def signup_form_handler():
"""
This function outputs the signup form
"""
fields = get_fields('Person')
field_types = Person({}).form_field_types()
print(fields)
print (field_types)
return {}
@app.post('/users/add/')
@app.post('/users/add')
def signup_action_handler():
"""
This function takes data from a signup form and creates a user
"""
# TODO have this called from whatever we're using for sign up
fields = get_fields('Person')
field_types = Person({}).form_field_types()
new_person = Person({
'given_name':'Chris',
'family_name':'Thorpe',
'email':'[email protected]',
'job_title':'Head of Technology',
'description':'An open data and open standards and reuse nerd',
'knows_about': 'Open standards, open data, reuse, some technology, digital strategy',
'work_location':'South East',
'seeks': 'Enlightenment, peace and tranquility.',
'cc__digital_journey': 'It\'s embedded into what we do across all areas and our daily practices',
'cc__validated_mail': True,
'cc_created_at': None,
'cc__consent': True,
'cc__status': '100',
'cc__consent_text': 'consent text',
'works_for': {
'identifier':'wearecast.org.uk',
'name': 'CAST'
}
})
if new_person.has_errors():
errors = new_person.has_errors()
else:
errors = None
# TODO persist the model in InnoDB or similar
# TODO notify Slack of new signup
# TODO fire signup email
#asyncio.run(send_email('signup', [new_person.as_dict()['email']], 'Welcome to Coffee Connections', {'hostname':'http://localhost:5000', 'person': new_person.as_dict()}))
return {'errors': errors, 'person':new_person.as_json()}
@app.get('/users/confirm-email/<string:identifier>/<string:email_confirmation_secret>')
def confirm_user_handler(identifier, email_confirmation_secret):
# TODO get user
# TODO show as a web page, style to be defined
# TODO notify Slack of confirmation
return {'identifier':identifier, 'secret':email_confirmation_secret, 'validated':True}
@app.get('/admin/users/approve/<string:identifier>/<string:approve_user_secret>')
def admin_user_approve_handler(identifier, approve_user_secret):
# TODO approve of a user of coffee connections - as a button in Slack
return {'identifier':identifier, 'secret':approve_user_secret, 'validated':True}
@app.get('/admin/matches/create')
# TODO think about what this does. Does it create a new match table? Does it populate with guessed matches
def admin_create_matches_handler():
return 'admin/ create matches'
@app.get('/admin/matches/view')
def admin_view_matches_handler():
# TODO show matches
return 'admin/ view matches'
@app.get('/admin/matches/test')
def admin_test_matches_handler():
# TODO show form which will approve the sending, make it have some friction
return 'admin/ send test matches'
@app.get('/admin/matches/test/confirmed')
def admin_test_matches_confirm_handler():
return 'admin/ send test matches confirmed'
@app.get('/admin/matches/send')
def admin_send_matches_handler():
# TODO show form which will approve the sending, make it have some friction
return 'admin/ send live matches'
@app.get('/admin/matches/send/confirmed')
def admin_send_matches_confirm_handler():
return 'admin/ send live matches confirmed'
@app.get('/admin/followups/test')
def admin_test_followups_handler():
# TODO show form in Slack which will approve the sending, make it have some friction
return 'admin/ send test matches'
@app.get('/admin/followups/test/confirmed')
def admin_test_followups_confirm_handler():
return 'admin/ send test matches confirmed'
@app.get('/admin/followups/send')
def admin_send_followups_handler():
# TODO show form in Slack which will approve the sending, make it have some friction
return 'admin/ send live matches'
@app.get('/admin/followups/send/confirmed')
def admin_send_followups_confirm_handler():
return 'admin/ send live matches confirmed'
@app.get('/users/import')
def csv_user_loader():
"""
This function loads historic data and creates individual files for users and a collected file of all users, persisted in S3
"""
row_count = 0
import_count = 0
match_count = 0
match_error_count = 0
historic_users = {}
unique_ids = []
# first off, we'll import the users into the historic users dictionary
with open('livedata/cc_users.csv', newline='') as csvfile:
cc_historic_users = csv.reader(csvfile, delimiter=',')
for historic_user in cc_historic_users:
# first row contains the labels
if row_count == 0:
# map the labels from the CSV to the new variable names from schema.org
fields = map_historic_data_fields(historic_user)
row_count += 1
else:
# ignore empty rows
if len(historic_user) > 0:
# joing field names and fields from the CSV
userdict = {key:value for (key, value) in list(zip(fields, historic_user))}
# append the original id to an array of unique ids
id = userdict['cc__historic_id']
unique_ids.append(id)
# combine the data from two fields to form the organisation
if len(userdict['works_for-name']) > 0:
userdict['works_for'] = {
'identifier':userdict['works_for-identifier'],
'name': userdict['works_for-name']
}
# and then remove those two fields from the user dictionary
del userdict['works_for-name']
del userdict['works_for-identifier']
# we're dealing with historic data, so we'll set the email validation field to True as they're existing users who've been mailed
userdict['cc__validated_mail'] = True
# then load the Person pydantic model to check for validation errors
person = Person(userdict)
if person.has_errors():
print (person.has_errors())
else:
# if there are no errors, we'll add them to the historic users dictionary and increment the import counter
import_count += 1
historic_users[id] = person.as_dict()
# increment the row counter so we can compare rows in the dataset to number of successful imports
row_count += 1
# now we'll iterate through the previous matches
#TODO check with Coffee Connections team about run 16. Was it sent?
with open('livedata/cc_matches.csv', newline='') as csvfile:
previous_matches = csv.reader(csvfile, delimiter=',')
match_row_count = 0
for previous_match in previous_matches:
# ignore the first row as that's labels
if match_row_count > 0:
match_1 = previous_match[1]
match_2 = previous_match[2]
run = previous_match[3]
# the data contains some matches where the user is no longer in the dataset
try:
historic_users[match_1]['cc__matches'].append(historic_users[match_2]['identifier'])
if run not in historic_users[match_1]['cc__runs']:
historic_users[match_1]['cc__runs'].append(run)
historic_users[match_2]['cc__matches'].append(historic_users[match_1]['identifier'])
if run not in historic_users[match_2]['cc__runs']:
historic_users[match_2]['cc__runs'].append(run)
match_count += 1
except:
print ((match_1, match_2, run))
match_error_count += 1
# increment the match row count. due to the missing user this will not match the count of the matches
match_row_count += 1
# now that we've associated the users with the matches we'll iterate through again to check for errors
processed_users = {}
for id in historic_users:
persondict = historic_users[id]
person = Person(persondict)
if person.has_errors():
print (person.has_errors())
else:
processed_user = person.as_dict()
processed_users[processed_user['identifier']] = processed_user
#
match_row_count -= 1
row_count -=1
return {'users':processed_users, 'row_count':row_count, 'import_count':import_count, 'unique_id_count':len(unique_ids), 'match_count':match_count, 'match_row_count':match_row_count, 'match_error_count':match_error_count}