-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfsubmit.py
448 lines (396 loc) · 12.7 KB
/
fsubmit.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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
from fhelpers import cursor_execute, cursor_fetch, get_list_of_values, check_submitted_location
# Constant variables for inputs that require numbers
SQUARE_METERS_MIN = 30
SQUARE_METERS_MAX = 200
RENTAL_MIN = 100
RENTAL_MAX = 2000
BEDROOMS_MIN = 1
BEDROOMS_MAX = 4
BATHROOMS_MIN = 1
BATHROOMS_MAX = 2
def validate_submitted_digits(square_meters, rental, bedrooms, bathrooms):
'''
Checks if user has input valid digits in the digit-required fields
or ranged fields
'''
must_be_numbers = [
{
"form_data": square_meters,
"min": SQUARE_METERS_MIN,
"max": SQUARE_METERS_MAX,
"field_name": "Square Meters"
},
{
"form_data": rental,
"min": RENTAL_MIN,
"max": RENTAL_MAX,
"field_name": "Rental"
},
{
"form_data": bedrooms,
"min": BEDROOMS_MIN,
"max": BEDROOMS_MAX,
"field_name": "Bedrooms"
},
{
"form_data": bathrooms,
"min": BATHROOMS_MIN,
"max": BATHROOMS_MAX,
"field_name": "Bathrooms"
},
]
# Iterate through all must-be-numbers to validate them
for number in must_be_numbers:
form_data = number['form_data']
field_name = number['field_name']
# Ensure the digit value is not blank or None
if not bool(form_data):
raise ValueError(
f'Invalid input for {field_name}. Please enter valid numbers.'
)
# If request came from sumbit.html or edit_submission.html value should be a string
elif isinstance(form_data, str):
# Ensure the value is a digit
if not form_data.isdigit():
raise ValueError(
f'''
Invalid input for {field_name}. Please enter a valid number.
'''
)
# Validate the range of each number if its within proper range
form_data = int(form_data)
if (
form_data < number['min']
) or (
form_data > number['max']
):
raise ValueError(
f'''
Invalid {field_name}. Number must be between {number["min"]}
and {number["max"]}.
'''
)
else:
raise ValueError(
f'''
Invalid input type for {field_name}. Please enter a valid number
or range.'''
)
def validate_submitted_location(
city,
municipality,
region,
):
'''
Checks if the selected location values are valid by comparing them
with those that are stored in the database.
The function respects the dependencies between location, so that
specific cities will come along with specific municipalities and
regions as well.
It also handles the situation where the user has selected 'any' as
his desired move location (aka destination).
'''
# City validation
# Fetch all valid cities from the database
query = '''
SELECT DISTINCT city FROM cities;
'''
cities_json = cursor_fetch(query)
# Extract city values into lists
cities = get_list_of_values(cities_json, 'city')
# Append 'any' as valid value if user has selected that option
if city == 'any':
cities.append(city)
# Validate submitted city location or destination value
check_submitted_location(
city,
cities,
'Invalid city value. Not found in the database'
)
if city in cities and city != 'any':
# Fetch id of selected city to use it for valid municipality fetch
query = '''
SELECT id FROM cities
WHERE city = ?;
'''
city_id = cursor_fetch(
query,
city
)
elif city == 'any':
pass
else:
raise ValueError(
'Invalid city value. Not found in the database'
)
# Municipality validation
# Declare an empty list that may be needed even empty in region check
municipalities=[]
if city in cities and city != 'any' and municipality != 'any':
# Fetch all valid municipalities from the database
query = '''
SELECT DISTINCT municipality FROM municipalities
WHERE city_id = ?;
'''
municipalities_json = cursor_fetch(
query,
city_id[0]['id']
)
# Extract municipality values into lists
municipalities = get_list_of_values(municipalities_json, 'municipality')
# Append 'any' as valid value if user has selected that option
if municipality == 'any':
municipalities.append(municipality)
# Validate submitted municipality location or destination value
check_submitted_location(
municipality,
municipalities,
'Invalid municipality value. Not found in the database'
)
# Fetch id of selected municipality to use it for valid region fetch
query = '''
SELECT id FROM municipalities
WHERE municipality = ?;
'''
municipality_id = cursor_fetch(
query,
municipality
)
elif municipality == 'any':
pass
else:
raise ValueError(
'Invalid municipality value. Not found in the database'
)
# Region validation
if (
(
city in cities and city != 'any'
) and (
municipality in municipalities and municipality != 'any'
) and (
region != 'any'
)
):
# Fetch all valid regions from the database
query = '''
SELECT DISTINCT region FROM regions
WHERE municipality_id = ?;
'''
regions_json = cursor_fetch(
query,
municipality_id[0]['id']
)
# Extract region values into lists
regions = get_list_of_values(regions_json, 'region')
# Append 'any' as valid region value if it's destination check
if region == 'any':
regions.append('any')
# Validate submitted region location or destination value
check_submitted_location(
region,
regions,
'Invalid region value. Not found in the database'
)
elif region == 'any':
pass
else:
raise ValueError(
'Invalid region value. Not found in the database'
)
def submission_validation(
all_field_values,
exposure,
house_type,
square_meters,
rental,
bedrooms,
bathrooms,
city,
municipality,
region,
city_destination,
municipality_destination,
region_destination
):
'''
Checks for valid input form in submit/edited_submission routes.
If any new input form will be available in the future add here
conditionals for backend validation check
'''
# Ensure that all required fields are not blank
if not all(field for field in all_field_values):
raise ValueError('Some required fields weren\'t filled in.')
# Declare a list of valid options for exposure value
exposure_valid_options = [
'public',
'private',
]
# Declare a list of valid options for house_type value
house_type_options = [
'studio',
'flat',
'maisonette',
'semi-detached_house',
'detached_house',
'mansion',
]
# Ensure exposure and house_type values are valid
if (
exposure not in exposure_valid_options or
house_type not in house_type_options
):
raise ValueError('Invalid exposure and/or house type.')
# Ensure submitted digit-required values or value ranges are valid
validate_submitted_digits(
square_meters,
rental,
bedrooms,
bathrooms
)
# Ensure submitted location values are valid
validate_submitted_location(
city,
municipality,
region
)
# Ensure submitted destination values are valid
validate_submitted_location(
city_destination,
municipality_destination,
region_destination
)
return True
def user_submissions_exist(user_id):
'''
Checks if the user has at least one submission in the database and
after that it looks for user's primary submission. In case it finds
the primary submission it returns True; otherwise False.
This returned boolean will determine the primarySubmission checkbox
in the html forms at @submit and @edited_submission routes
'''
# Select all the user's submissions
query = '''
SELECT * FROM submissions
WHERE user_id = ?;
'''
user_submissions = cursor_fetch(
query,
user_id
)
# Check if the user has at least 1 submission
if len(user_submissions) < 1:
return False
else:
# Look for user's primary submission
for submission in user_submissions:
if submission['primary_submission'] == 1:
return True
return False
def determine_primary_submission_status(
primary_submission,
primary_submission_locked,
user_id
):
'''
Determines the status of the primarySubmission checkbox in html
forms at @submit and @edited_submission routes.
Returns True (checked checkbox) in case the user has no submissions.
Returns True in case the user saves an already primary submission or
in case he sets a new submission or an edited submission as the new
primary. In that case it also set all other submissions to
non-primary statuses.
Otherwise returns False (unchecked checkbox).
'''
# Check if the user has at least one submission that is primary
if not user_submissions_exist(user_id):
return True
elif primary_submission_locked or primary_submission:
# Set primary_submission status to False for all user's submissions
query = '''
UPDATE submissions SET
primary_submission = ?
WHERE user_id = ?;
'''
cursor_execute(
query,
False,
user_id
)
return True
else:
return False
def handle_primary_submission_upon_deletion(submission_id, user_id):
'''
Handles primary submission in database upon submission deletion.
In case the submission to be deleted has non-primary status, the
function returns False and the submission is deleted instantly
(code in route).
In case the submission to be deleted is of primary status but the
user has also more than one submissions in the database, then the
submission is deleted and the oldest submission is set as the new
primary one. In that case the function returns True and the `if`
conditional statement in the route gets passed.
'''
# Fetch primary submission status of submission to be deleted
query = '''
SELECT primary_submission FROM submissions
WHERE id = ?
AND user_id = ?;
'''
primary_submission_data = cursor_fetch(
query,
submission_id,
user_id
)
# Fetch all the user's submissions
query = '''
SELECT * FROM submissions
WHERE user_id = ?;
'''
user_submissions = cursor_fetch(
query,
user_id
)
# Check if submission to be deleted isn't sole and if is primary
if (
primary_submission_data[0]['primary_submission'] == 1
) and (
len(user_submissions) > 1
):
# Delete edited submission from database
query = '''
DELETE FROM submissions
WHERE id = ?
AND user_id = ?;
'''
cursor_execute(
query,
submission_id,
user_id
)
# Refetch all the user's submissions from updated database
query = '''
SELECT * FROM submissions
WHERE user_id = ?;
'''
user_submissions = cursor_fetch(
query,
user_id
)
# Set the oldest submission as primary
query = '''
UPDATE submissions SET
primary_submission = ?
WHERE id = ?
AND user_id = ?;
'''
cursor_execute(
query,
True,
user_submissions[0]['id'],
user_id
)
return True
else:
return False