-
Notifications
You must be signed in to change notification settings - Fork 16
/
default.py
executable file
·1865 lines (1686 loc) · 83.7 KB
/
default.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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
import re
import random
import urllib.parse
from json import dumps
from collections import OrderedDict
from OZfunc import (
clear_reservation,
nice_species_name, get_common_name, get_common_names, sponsorable_children_query,
language, __make_user_code, raise_incorrect_url, require_https_if_nonlocal, add_the,
otts2ids, nodes_info_from_array, nodes_info_from_string, extract_summary)
""" Some settings for sponsorship"""
try:
reservation_time_limit = float(myconf.take('sponsorship.reservation_time_limit_mins')) * 60.0
except:
reservation_time_limit = 360.0 #seconds
try:
unpaid_time_limit = float(myconf.take('sponsorship.unpaid_time_limit_mins')) * 60.0
except:
unpaid_time_limit = 2.0*24.0*60.0*60.0 #seconds
""" generally useful functions """
def time_diff(startTime,endTime):
return (endTime-startTime)
"""Standard web2py stuff"""
def index():
"""
Collect all information required for the home page
"""
# OTTs from the tree_startpoints table
startpoints_ott_map, hrefs, images, titles, text_titles = {}, {}, {}, {}, {}
carousel, anim, threatened = [], [], []
for r in db(
(db.tree_startpoints.category.startswith('homepage')) &
(db.tree_startpoints.partner_identifier == None)
).select(
db.tree_startpoints.ott, db.tree_startpoints.category,
db.tree_startpoints.image_url, db.tree_startpoints.tour_identifier,
orderby = db.tree_startpoints.id):
key = r.tour_identifier or str(r.ott)
if r.category.endswith("main"):
carousel.append(key)
elif r.category.endswith("anim"):
anim.append(key)
elif r.category.endswith("red"):
threatened.append(key)
if r.image_url:
images[key] = {'url': r.image_url}
if r.tour_identifier:
hrefs[key] = URL('life/' + r.tour_identifier)
title = db(db.tours.identifier == r.tour_identifier).select(db.tours.name).first()
text_titles[key] = title.name if title else r.tour_identifier
else:
text_titles[key] = ""
if r.ott:
# We might still want to find e.g. an image, even if we are looking at a tour
startpoints_ott_map[r.ott] = key
# Pick 5 random threatened spp
random.seed(request.now.month*100 + request.now.day)
if len(threatened) > 5:
threatened = random.sample(threatened, 5)
image_required = set(carousel + threatened)
keys = set(anim) | image_required
# Remove the unused threatened ones
startpoints_ott_map = {k: v for k, v in startpoints_ott_map.items() if v in keys}
# OTTs from the reservations table (i.e. sponsored)
query = (db.reservations.verified_time != None) & \
((db.reservations.deactivated == None) | (db.reservations.deactivated == "")) & \
(db.reservations.verified_preferred_image_src != None)
sponsored_rows = db(query).select(
db.reservations.OTT_ID,
db.reservations.name,
db.reservations.user_nondefault_image,
db.reservations.verified_kind,
db.reservations.verified_name,
db.reservations.verified_more_info,
db.reservations.verified_preferred_image_src,
db.reservations.verified_preferred_image_src_id,
orderby=~db.reservations.verified_time|db.reservations.reserve_time,
limitby=(0, 20)
)
spon_leaf_otts = set()
sponsored_by_ott = {}
for r in sponsored_rows:
sponsored_by_ott[r.OTT_ID] = r
hrefs[r.OTT_ID] = URL('life/@=%d' % r.OTT_ID, url_encode=False)
spon_leaf_otts.add(r.OTT_ID)
titles[r.OTT_ID] = r.name
for ott, key in startpoints_ott_map.items():
if key not in hrefs:
hrefs[key] = URL('life/@=%d' % ott, url_encode=False)
# Names
st_leaf_otts, st_node_otts, has_vernacular = set(), set(), set()
st_leaf_for_node_otts = {}
# Look up scientific names for startpoint otts
for r in db(db.ordered_leaves.ott.belongs(startpoints_ott_map.keys())).select(
db.ordered_leaves.ott, db.ordered_leaves.name):
st_leaf_otts.add(r.ott)
titles[r.ott] = r.name
# Look up scientific names and best PD image otts for all startpoint otts
for r in db(db.ordered_nodes.ott.belongs(list(startpoints_ott_map.keys()))).select(
db.ordered_nodes.ott, db.ordered_nodes.name, db.ordered_nodes.rpd1):
st_node_otts.add(r.ott)
if startpoints_ott_map[r.ott] in image_required:
st_leaf_for_node_otts[r.rpd1] = startpoints_ott_map[r.ott]
titles[r.ott] = r.name
# Add or change to vernacular names in the titles
for ott, vn in get_common_names(titles.keys(), return_nulls=True).items():
# Do one thing for the startpoints (simple names) ...
startpoint_key = startpoints_ott_map.get(ott, None)
if startpoint_key:
if not text_titles[startpoint_key]:
if vn is not None:
has_vernacular.add(startpoint_key)
text_titles[startpoint_key] = nice_species_name(
(titles[ott] if vn is None else None), vn, html=True,
leaf=ott not in st_node_otts, break_line=2)
# ... and another for the sponsored items (both common and sci in the string)
if vn is not None:
has_vernacular.add(ott)
titles[ott] = nice_species_name(
titles[ott], vn, html=True, leaf=ott not in st_node_otts,
first_upper=True, break_line=1)
titles.update(text_titles)
# Images
# Startpoint images
otts = st_leaf_otts | set(st_leaf_for_node_otts.keys())
for r in db(
(db.images_by_ott.ott.belongs(otts)) & (db.images_by_ott.overall_best_pd==1)
).select(
db.images_by_ott.ott, db.images_by_ott.src, db.images_by_ott.src_id):
key = startpoints_ott_map.get(r.ott, None) or st_leaf_for_node_otts.get(r.ott, None)
if key not in images:
images[key] = {'url':thumbnail_url(r.src, r.src_id)}
# Sponsored images
for r in db(
(db.images_by_ott.ott.belongs(spon_leaf_otts)) & (db.images_by_ott.overall_best_any==1)
).select(
db.images_by_ott.ott, db.images_by_ott.src, db.images_by_ott.src_id,
db.images_by_ott.rights, db.images_by_ott.licence):
reservations_row = sponsored_by_ott[r.ott]
if reservations_row.user_nondefault_image:
images[r.ott] = {'url':thumbnail_url(
reservations_row.verified_preferred_image_src,
reservations_row.verified_preferred_image_src_id)}
else:
images[r.ott] = {'url':thumbnail_url(r.src, r.src_id), 'rights':r.rights, 'licence': r.licence.split('(')[0]}
blank = {'url': URL('static','images/noImage_transparent.png')}
for key in titles.keys():
if key not in images:
images[key] = blank
return dict(
n_species=db(db.ordered_leaves).count(),
n_images=db(db.images_by_ott).count(),
quotes=[
db(db.quotes.quality >= 190).select(db.quotes.ALL, orderby='<random>', limitby=(0, 2)),
db((db.quotes.quality < 190) & (db.quotes.quality >= 100)).select(db.quotes.ALL, orderby='<random>', limitby=(0, 8))
],
news=[
dict(
heading=row.text_date if row.text_date else row.news_date.strftime("%d %B %Y").lstrip('0'),
body=row.html_description.replace(' class="thumbnail"', ' style="display:none"'),
thumbnail_href=row.thumbnail_href,
more_href=URL("timeline.html#news-item{}".format(row.id))
)
for row in db().select(db.news.ALL, orderby =~ db.news.news_date, limitby = (0, 5))
],
carousel=carousel, anim=anim, threatened=threatened, sponsored=sponsored_rows,
hrefs=hrefs, images=images, html_names=titles, has_vernacular=has_vernacular, add_the=add_the,
n_total_sponsored=db(db.reservations.PP_e_mail != None).count(distinct=db.reservations.PP_e_mail),
n_sponsored_leaves=db((db.reservations.verified_time != None) & ((db.reservations.deactivated == None) | (db.reservations.deactivated == ""))).count(),
menu_splash_images={
sub_menu[0]:URL('static', 'images/oz-newssplash-%s.jpg' % sub_menu[0].lower().replace("for ", ""))
for sub_menu in response.menu
}
)
def footer_sponsor_items():
"""
Three hardcoded images for groups that can be sponsored - appears on every page =>
should not make a db request
"""
return dict()
def homepage_animation_template():
"""
The html fragment used as a template for the embedded animation on the homepage
"""
return dict()
@require_https_if_nonlocal()
def user():
"""
exposes:
http://..../[app]/default/user/login
http://..../[app]/default/user/logout
http://..../[app]/default/user/register
http://..../[app]/default/user/profile
http://..../[app]/default/user/retrieve_password
http://..../[app]/default/user/change_password
http://..../[app]/default/user/manage_users (requires membership in
http://..../[app]/default/user/bulk_register
use @auth.requires_login()
@auth.requires_membership('group name')
@auth.requires_permission('read','table name',record_id)
to decorate functions that need access control
"""
#from http://www.web2pyslices.com/slice/show/1642/login-with-username-and-email - allow username OR email (to allow 'guest' account
if 'login' in request.args:
db.auth_user.username.label = T("Username or Email")
auth.settings.login_userfield = 'username'
if request.vars.username and not IS_EMAIL()(request.vars.username)[1]:
auth.settings.login_userfield = 'email'
request.vars.email = request.vars.username
request.post_vars.email = request.vars.email
request.vars.username = None
request.post_vars.username = None
return dict(form=auth())
return dict(form=auth())
## General pages ##
def custom_400():
return {}
def custom_404():
return {}
## Main leaf sponsorship routines ##
def sponsor_leaf():
"""
The main sponsorship page, which does most of the validation
"""
return sponsor_leaf_check(
use_form_data=(request.extension == "load"), form_data_to_db=False)
def sponsor_leaf_check(use_form_data, form_data_to_db):
"""
Take an open tree ID and from that ...
1. figure out status of leaf and return the correct page
these options will all forward to different pages (view listed in square
brackets below), selected in the following order:
* maintenance [spl_maintenance.html] - the site is in maintenance mode,
and we don't want to allow any access to reservations data
* no status set [spl_error.html] - something went very wrong
Shown even when sponsorship is turned off
* sponsored [spl_sponsored.html] - it's already been sponsored
* invalid [spl_invalid.html] - not an actual sponsorable OTTid
* banned [spl_banned.html] - cannot sponsor this
* available on main site [spl_elsewhere.html] - it may be available but this
onezoom instance doesn't allow sponsorship (e.g. in a museum)
Shown if sponsorship is on.
* unverified [spl_unverified.html] - it's already been sponsored but the
details haven't been verified yet
* unverified waiting for payment [spl_waitpay.html] - has been sponsored but
paypal hasn't sent us confirmation (could be that they didn't actually
pay, so may become free after a few days)
Shown when sponsorship is allowed and not in maintenance mode
* reserved [spl_reserved.html] - another user was active on this page
recently and it's being reserved for them for a few minutes
* available [sponsor_leaf.html] - the leaf is fully available, so proceed
* available only to user [sponsor_leaf.html] - available for this user only
(the function also provides the current best picture for that leaf if one exists)
2. Update the reservations table with new # of views, last view time / user ids etc.
3. Collect the name, price and EOL ids from the database and return them to the page
4. Look up the partner_taxa table, find the ranges of leaf ids that will trigger a
partner deal, check if this leaf id is within any range, and if so, find the
partner_identifier and return the details to the page for that partner
(taken from the partners table) ### NOT YET DONE ###
Database info that needs to be handled by this function:
OTT_ID
name
num_views
last_view
reserve_time
user_registration_id
user_id (? - later)
e_mail
twitter_name
allow_contact
user_sponsor_kind
user_sponsor_name
user_more_info
user_preferred_image_src
user_preferred_image_src_id
user_updated_time
user_paid
user_message_OZ
This function DOES NOT need to update the sponsor or verified info tables because
these are handled separately. Additionally, e-mail, address and name as well as
receipt should be captured from Paypal
"""
# initialise status flag (it will get updated if all is OK)
status = ""
try:
maint = int(myconf.take('sponsorship.maintenance_mins'))
except:
maint = 0
if maint:
status = "maintenance"
max_price = db.prices.price.max()
max_global_price = db().select(max_price).first()[max_price] / 100
min_price = db.prices.price.min()
min_global_price = db().select(min_price).first()[min_price] / 100
if (request.vars.get('form_reservation_code')):
form_reservation_code = request.vars.form_reservation_code
else:
form_reservation_code = __make_user_code()
# default to not allowing sponsorships, unless actively turned on in appconfig.ini
# even if turned on, sometimes (e.g. museum display on main OZ site) we shut off
# sponsoring anyway by passing a url param.
allow_sponsorship = False
if request.vars.no_sponsoring:
pass
else:
try:
spons = myconf.take('sponsorship.allow_sponsorship')
if spons.lower() in ['1', 'all']:
allow_sponsorship = True
elif spons.lower() in ['0', 'none']:
allow_sponsorship = False
# If anything other than '1' or 'all', treat this as a role, e.g. "manager"
elif auth.has_membership(role=spons):
allow_sponsorship = True
except:
pass
# initialise other variables that will be passed on to the page
sp_name = common_name = the_long_name = default_image = None
release_time = 0 #when this will become free, in seconds
try:
EoL_API_key=myconf.take('api.eol_api_key')
except:
EoL_API_key=""
# now search for OTTID in the leaf table
try:
OTT_ID_Varin = int(request.vars.get('ott'))
leaf_entry = db(db.ordered_leaves.ott == OTT_ID_Varin).select().first()
common_name = get_common_name(OTT_ID_Varin)
sp_name = leaf_entry.name
long_name = nice_species_name(sp_name, common_name, html=True, leaf=True, the=False)
the_long_name = nice_species_name(sp_name, common_name, html=True, leaf=True, the=True)
except:
OTT_ID_Varin = None
leaf_entry = {}
if ((not leaf_entry) or # invalid if not in ordered_leaves
(leaf_entry.ott is None) or # invalid if no OTT ID
(' ' not in leaf_entry.name)): # invalid if not a sp. name (no space/underscore)
iucn_code = None
status = "invalid" # will override maintenance
else:
iucn_code = getattr(
db(db.iucn.ott == OTT_ID_Varin).select(db.iucn.status_code).first(),
'status_code',
None)
if status == "": # still need to figure out status, but should be able to get data
# we might come into this with a partner set in request.vars (e.g. LinnSoc)
partner = request.vars.get('partner')
"""
TO DO & CHECK - Allows specific parts of the tree to be associated with a partner
if partner is None:
#check through partner_taxa for leaves that might match this one
partner = db((~db.partner_taxa.deactived) &
(db.partner_taxa.is_leaf == True) &
(db.partner_taxa.ott == OTT_ID_Varin)
).select(db.partner_taxa.partner_identifier).first()
if partner is None:
#pull out potential partner *nodes* that we might need to check
#also check if this leaf lies within any of the node ranges
#this should include a join with ordered_nodes to get the ranges, & a select
partner = db((~db.partner_taxa.deactived) &
(db.partner_taxa.is_leaf == False) &
(OTT_ID_Varin >= db.ordered_nodes.leaf_lft) &
(OTT_ID_Varin <= db.ordered_nodes.leaf_rgt) &
(db.ordered_nodes.ott == db.partner.ott).first()
).select(db.partner_taxa.partner_identifier)
"""
try:
if partner is None:
raise AttributeError
partner_data = db(db.partners.partner_identifier == partner).select(
db.partners.ALL).first().as_dict() # NB: this could be null
except AttributeError:
partner_data = {}
# find out if the leaf is banned
if db(db.banned.ott == OTT_ID_Varin).count() >= 1:
status = "banned"
# we need to update the reservations table regardless of banned status)
reservation_query = db(db.reservations.OTT_ID == OTT_ID_Varin)
reservation_row = reservation_query.select().first()
if reservation_row is None:
# there is no row in the database for this case so add one
if (status == ""):
status = "available"
if status == "available" and allow_sponsorship:
db.reservations.insert(
OTT_ID = OTT_ID_Varin,
name=leaf_entry.name,
last_view=request.now,
num_views=1,
reserve_time=request.now,
user_registration_id=form_reservation_code)
else:
# update with full viewing data but no reservation, even if e.g. banned
db.reservations.insert(
OTT_ID = OTT_ID_Varin,
name=leaf_entry.name,
last_view=request.now,
num_views=1)
else:
# there is already a row in the database so update if this is the main visit
if request.function == 'sponsor_leaf' and request.extension == "html":
reservation_query.update(
last_view=request.now,
num_views=reservation_row.num_views+1,
name=sp_name)
# this may be available (because valid) but could be
# sponsored, unverified, reserved or still available
# easiest cases to rule out are relating sponsored and unverified cases.
# In either case they would appear in the leger
ledger_user_name = reservation_row.user_sponsor_name
ledger_PP_transaction_code = reservation_row.PP_transaction_code
ledger_verified_time = reservation_row.verified_time
# We know something is fully sponsored if PP transaction code is filled out
# NB: this could be with us typing "yet to be paid" in which case
# verified_paid can be NULL, so "verified paid " should not be used as a
# test of whether something is available or not
# For forked sites, we do not pass PP transaction code (confidential), so we
# have to check first if verified.
# Need to have another option here if verified_time is too long ago - we
# should move this to the expired_reservations table and clear it.
if (ledger_verified_time):
status = "sponsored"
elif status != "banned":
if (ledger_user_name):
# something has been filled in
if (ledger_PP_transaction_code):
#we have a code (or have reserved this taxon)
status = "unverified"
else:
# unverified and unpaid - test time
startTime = reservation_row.reserve_time
endTime = request.now
timesince = ((endTime-startTime).total_seconds())
# now we check if the time is too great
if (timesince < (unpaid_time_limit)):
status = "unverified waiting for payment"
else:
# We've waited too long and can zap the personal data
# previously in the table then set available
clear_reservation(OTT_ID_Varin)
# Note that this e.g. clears deactivated taxa, etc etc. Even
# if status == available, allow_sponsorship can be False
# status is then used to decide the text to show the user
status = "available"
else:
# The page has no user name entered & is also valid (not banned etc)
# it could only be reserved or available
# First thing is to determine time difference since reserved
startTime = reservation_row.reserve_time
endTime = request.now
if (startTime == None):
status = "available"
# reserve the leaf because there is no reservetime on record
if allow_sponsorship:
reservation_query.update(
name=sp_name,
reserve_time=request.now,
user_registration_id=form_reservation_code)
else:
# compare times to figure out if there is a time difference
timesince = ((endTime-startTime).total_seconds())
if (timesince < (reservation_time_limit)):
release_time = reservation_time_limit - timesince
# we may be reserved if it wasn't us
if(form_reservation_code == reservation_row.user_registration_id):
# it was the same user anyway so reset timer
status = "available only to user"
if allow_sponsorship:
reservation_query.update(
name=sp_name,
reserve_time=request.now)
else:
status = "reserved"
else:
# it's available still
status = "available"
# reserve the leaf because there is no reservetime on record
if allow_sponsorship:
reservation_query.update(
name=sp_name,
reserve_time = request.now,
user_registration_id = form_reservation_code)
#re-do the query since we might have added the row ID now
reservation_row = reservation_query.select().first()
if reservation_row is None:
raise HTTP(400,"Error: row is not defined. Please try reloading the page")
#get the best picture for this ott, if there is one.
query = (db.images_by_ott.ott == OTT_ID_Varin)
query &= (db.images_by_ott.overall_best_any == True)
default_image = db(query).select(
db.images_by_ott.ott, db.images_by_ott.src, db.images_by_ott.src_id,
db.images_by_ott.rights, db.images_by_ott.licence).first()
#also look at the nondefault images if present
if reservation_row.user_nondefault_image:
src_id = reservation_row.verified_preferred_image_src_id
src = reservation_row.verified_preferred_image_src
query = (db.images_by_ott.src_id == src_id) & (db.images_by_ott.src == src)
user_image = db(query).select(
db.images_by_ott.ott, db.images_by_ott.src, db.images_by_ott.src_id,
db.images_by_ott.rights, db.images_by_ott.licence).first()
else:
user_image=None
#once we have got this far, we can show certain pages
#even in maintenance mode or where allow_sponsorship is not True, e.g. if
#a leaf is already sponsored, or is banned from sponsorship
if status == "maintenance":
response.view = request.controller + "/spl_maintenance." + request.extension
return dict(mins=str(maint))
elif status == "sponsored":
response.view = request.controller + "/spl_sponsored." + request.extension
return dict(
species_name = sp_name,
js_species_name = dumps(sp_name),
common_name = common_name,
js_common_name = dumps(common_name.capitalize() if common_name else None),
long_name = long_name,
iucn_code = iucn_code,
default_image = default_image,
user_image = user_image,
verified_kind = reservation_row.verified_kind,
verified_name = reservation_row.verified_name,
verified_more_info= reservation_row.verified_more_info)
elif status == "invalid":
response.view = request.controller + "/spl_invalid." + request.extension
return dict(OTT_ID=OTT_ID_Varin, species_name=sp_name)
elif not allow_sponsorship:
if status.startswith("available"):
response.view = request.controller + "/spl_elsewhere." + request.extension
else:
response.view = request.controller + "/spl_elsewhere_not." + request.extension
return dict(
js_species_name = dumps(sp_name),
js_common_name = dumps(common_name.capitalize() if common_name else None),
species_name = sp_name,
the_long_name = the_long_name,
iucn_code = iucn_code,
default_image = default_image,
ott = OTT_ID_Varin)
elif status.startswith("unverified"):
if status == "unverified waiting for payment":
response.view = request.controller + "/spl_waitpay." + request.extension
return dict(
species_name=sp_name,
the_long_name=the_long_name,
unpaid_time_limit_hours= int(unpaid_time_limit/60.0/60.0))
else:
response.view = request.controller + "/spl_unverified." + request.extension
return dict(species_name = sp_name)
elif status == "banned":
response.view = request.controller + "/spl_banned." + request.extension
return dict(
species_name = sp_name,
js_species_name = dumps(sp_name),
common_name = common_name,
js_common_name = dumps(common_name.capitalize() if common_name else None),
long_name = long_name,
default_image = default_image,
user_image = user_image)
elif status == "reserved":
response.view = request.controller + "/spl_reserved." + request.extension
return dict(
species_name = sp_name,
the_long_name = the_long_name,
release_time = release_time)
elif leaf_entry.price is None:
response.view = request.controller + "/spl_banned." + request.extension
return dict(
species_name = sp_name,
js_species_name = dumps(sp_name),
common_name = common_name,
js_common_name = dumps(common_name.capitalize() if common_name else None),
long_name = long_name,
default_image = default_image)
elif status.startswith("available"):
response.view = request.controller + "/sponsor_leaf." + request.extension
# Can sponsor here, so go through to the main sponsor_leaf page
form = None
validated = None
price = 0.01*float(leaf_entry.price)
if use_form_data:
# This is the real form
form = SQLFORM(db.reservations, reservation_row,
fields=[
## List all fields that will be updated by sponsor leaf
# Writable by the user
'e_mail','allow_contact','twitter_name', 'user_sponsor_kind',
'user_sponsor_name', 'user_more_info', 'user_donor_title',
'user_donor_name', 'user_donor_show', 'user_paid', 'user_message_OZ',
'user_nondefault_image', 'user_preferred_image_src',
'user_preferred_image_src_id','user_giftaid', 'user_sponsor_lang',
# writeable=False -> filled out on validation
'name', 'reserve_time', 'asking_price', 'user_updated_time',
'asking_price', 'user_updated_time', 'sponsorship_duration_days',
'partner_name', 'partner_percentage'
],
deletable = False)
if form.accepts(
request.vars, # use both GET + POST vars: GET vars passed when accessed via LOAD
session=None,
formname="main_sponsor_form",
dbio=form_data_to_db,
onvalidation=lambda x: valid_spons(x, sp_name, price, partner_data)):
validated = True # indicates to follow the form submission to paypal
elif form.errors:
validated = False
else:
pass # Simply show the form
return dict(
form = form,
validated = validated,
id = reservation_row.id,
OTT_ID = OTT_ID_Varin,
EOL_ID = leaf_entry.get('eol', -1),
species_name = sp_name,
js_species_name = dumps(sp_name),
common_name = common_name,
js_common_name = dumps(common_name.capitalize() if common_name else None),
the_long_name = the_long_name,
iucn_code = iucn_code,
price = 0.01*float(leaf_entry.price),
default_image = default_image,
form_reservation_code = form_reservation_code,
percent_crop_expansion= percent_crop_expansion,
partner_data = partner_data,
EoL_API_key = EoL_API_key,
max_global_price = max_global_price,
min_global_price = min_global_price)
else:
#should never happen
response.view = request.controller + "/spl_error." + request.extension
return dict(OTT_ID = OTT_ID_Varin)
def sponsor_pay():
"""
Actually save the payment details in the db, and then redirect to a payments system,
e.g. paypal
"""
result = sponsor_leaf_check(use_form_data=True, form_data_to_db=True)
if not result.get('validated', None):
# Keep trying to validate, using the sponsor_leaf views
return result
else:
# Jump out to paypal
db_saved = result['form'].vars
OTT_ID_str = str(int(result['OTT_ID'])) # this is the only field not in the form
try:
# redirect the user to a paypal page that (if completed) triggers paypal to then visit
# an OZ page, confirming payment: this is called an IPN. Details in pp_process_post.html
try:
paypal_url = myconf.take('paypal.url')
if not paypal_url:
raise ValueError('blank paypal config')
except:
paypal_url = 'https://www.sandbox.paypal.com'
try:
paypal_notify_string = (
'¬ify_url=' + myconf.take('paypal.notify_url')
+ '/pp_process_post.html/' + OTT_ID_str)
except:
paypal_notify_string = ''
paypal_url += (
'/cgi-bin/webscr'
'?cmd=_donations'
'&business=mail@onezoom.org'
'&item_name=Donation+to+OneZoom+({sp_name})'
'&item_number=leaf+sponsorship+-+{sp_name}'
'&return={ret_url}'
'{notify_string}'
'&amount={amount}'
'¤cy_code=GBP'.format(
sp_name=urllib.parse.quote(db_saved.name),
ret_url=URL("sponsor_thanks.html", scheme=True, host=True),
notify_string=paypal_notify_string,
amount=urllib.parse.quote('{:.2f}'.format(db_saved.user_paid))))
redirect(paypal_url)
except:
raise
error="we couldn't find your leaf sponsorship information."
response.view = request.controller + "/sponsor_pay." + request.extension
return(dict(error=error, ott=request.vars.get('ott') or '<no available ID>'))
def valid_spons(form, species_name, price_pounds, partner_data):
"""
Do all this using custom validation as some is quite intricate
"""
max_chars = 30
# Validate user-input vars
if len(form.vars.user_sponsor_name or "") == 0:
form.errors.user_sponsor_name = T("You must enter some sponsor text")
elif len(form.vars.user_sponsor_name or "") > max_chars:
form.errors.user_sponsor_name = T("Text too long: max %s characters") % (max_chars, )
if form.vars.user_sponsor_kind == "by" and not form.vars.user_donor_name:
form.vars.user_donor_name = form.vars.user_sponsor_name
if len(form.vars.user_more_info or "") > max_chars:
form.errors.user_more_info = T("Text too long: max %s characters") % (max_chars, )
if form.vars.user_sponsor_kind not in ['by','for']:
form.errors.user_sponsor_kind = T("Sponsorship can only be 'by' or 'for'")
try:
if float(form.vars.user_paid) < price_pounds:
form.errors.user_paid = T("Please donate at least £%s to sponsor this leaf, or you could simply choose another leaf") % ("{:.2f}".format(price_pounds), )
except:
form.errors.user_paid = T("Please enter a valid number")
if form.vars.user_giftaid:
missing_title = not (form.vars.user_donor_title or "").strip()
if missing_title:
form.errors.user_donor_title_name = T("We need your title to be able to claim gift aid")
if not (form.vars.user_donor_name or "").strip():
if missing_title:
form.errors.user_donor_title_name = T("We need your name and title to be able to claim gift aid")
else:
form.errors.user_donor_title_name = T("We need your name to be able to claim gift aid")
# calculate writable=False vars, to insert
form.vars.name = species_name
form.vars.reserve_time = form.vars.user_updated_time = request.now
form.vars.user_sponsor_lang = (request.env.http_accept_language or '').lower()
form.vars.asking_price = price_pounds
form.vars.sponsorship_duration_days=365*4+1 ## 4 Years
form.vars.partner_name=partner_data.get('partner_identifier')
form.vars.partner_percentage=partner_data.get('percentage')
def sponsor_leaf_redirect():
"""
simply provides a link to jump out of an iframe
"""
return(dict(ott=request.vars.ott))
def sponsor_replace_page():
"""
The page displayed once the sponsor has jumped out to paypal in another frame
"""
try:
OTT_ID_Varin = int(request.vars.get('ott'))
row = db(db.reservations.OTT_ID == OTT_ID_Varin).select(
db.reservations.OTT_ID,
db.reservations.name,
db.reservations.user_sponsor_kind,
db.reservations.user_sponsor_name,
db.reservations.user_more_info,
db.reservations.user_paid,
db.reservations.PP_transaction_code).first()
if row is None:
raise IndexError(T("Could not match against a row in the database"))
return(dict(data=row))
except TypeError:
raise_incorrect_url(URL('index', scheme=True, host=True), T("Error - you gave no OTT number.") + " " + T("Go back to the home page"))
except Exception as e:
raise_incorrect_url(URL('index', scheme=True, host=True), str(e) + ". " + T("Go back to the home page"))
# TODO enabling edits and intelligent behaviour if a logged in user goes back to their own leaf
def sponsored():
"""
a simple paged list of recently sponsored species
"""
if len(request.args): page=int(request.args[0])
else: page=0
items_per_page=20
tot=None
resv = db.reservations
query = (resv.verified_time != None)
query &= ((resv.deactivated == None) | (resv.deactivated == ""))
if (request.vars.omit_nopics):
query = query & (resv.verified_preferred_image_src != None)
if (request.vars.getfirst('search_mesg')):
sanitized = "".join(
[ch for ch in request.vars.getfirst('search_mesg') if ch.isalnum()])
query = query & (resv.user_message_OZ.contains(sanitized))
if (request.vars.sum):
sum = resv.user_paid.sum()
tot = db(query).select(sum).first()[sum]
limitby=(page*items_per_page,(page+1)*items_per_page+1)
curr_rows = db(query).select(
resv.OTT_ID,
resv.name,
resv.user_nondefault_image,
resv.verified_kind,
resv.verified_name,
resv.verified_more_info,
resv.verified_preferred_image_src,
resv.verified_preferred_image_src_id,
orderby=~resv.verified_time|resv.reserve_time,
limitby=limitby
)
pds=set()
sci_names = {r.OTT_ID:r.name for r in curr_rows}
html_names = {ott:nice_species_name(sci_names[ott], vn, html=True, leaf=True, first_upper=True, break_line=2) for ott,vn in get_common_names(sci_names.keys(), return_nulls=True).items()}
#store the default image info (e.g. to get thumbnails, attribute correctly etc)
default_images = {r.ott:r for r in db(db.images_by_ott.ott.belongs(sci_names.keys()) & (db.images_by_ott.overall_best_any==1)).select(db.images_by_ott.ott, db.images_by_ott.src, db.images_by_ott.src_id, db.images_by_ott.rights, db.images_by_ott.licence, orderby=~db.images_by_ott.src)}
#also look at the nondefault images if present -
user_images = {}
for r in curr_rows:
if r.user_nondefault_image != 0:
user_images[r.OTT_ID] = db(
(db.images_by_ott.src_id==r.verified_preferred_image_src_id) &
(db.images_by_ott.src==r.verified_preferred_image_src)
).select(
db.images_by_ott.ott, db.images_by_ott.src, db.images_by_ott.src_id,
db.images_by_ott.rights, db.images_by_ott.licence,
orderby=~db.images_by_ott.src).first()
if (request.vars.highlight_pd):
pds.add(None) #ones without a src_id are public doman
pds |= set([l['src_id'] for k,l in default_images.items() if l['licence'].endswith(u'\u009C')]) #all pd pics should end with \u009C on the licence
pds |= set([l['src_id'] for k,l in user_images.items() if l['licence'].endswith(u'\u009C')]) #all pd pics should end with \u009C on the licence
return dict(rows=curr_rows, page=page, items_per_page=items_per_page, tot=tot, vars=request.vars, pds=pds, html_names=html_names, user_images=user_images, default_images=default_images)
def donor_list():
'''list donors by name. Check manage/SHOW_SPONSOR_SUMS.html to see what names to add.
'''
if len(request.args): page=int(request.args[0])
else: page=0
items_per_page=20
grouped_img_src = "GROUP_CONCAT(if(`user_nondefault_image`,`verified_preferred_image_src`,NULL))"
grouped_img_src_id = "GROUP_CONCAT(if(`user_nondefault_image`,`verified_preferred_image_src_id`,NULL))"
grouped_otts = "GROUP_CONCAT(`OTT_ID`)"
sum_paid = "COALESCE(SUM(`user_paid`),0)"
n_leaves = "COUNT(1)"
groupby = "IFNULL(verified_donor_name,id)"
limitby=(page*items_per_page,(page+1)*items_per_page+1)
curr_rows = db(db.reservations.verified_time != None).select(
grouped_img_src,
grouped_img_src_id,
grouped_otts,
sum_paid,
n_leaves,
db.reservations.verified_donor_name,
#the following fields are only of use for single displayed donors
db.reservations.name,
db.reservations.user_nondefault_image,
db.reservations.verified_kind,
db.reservations.verified_name,
db.reservations.verified_more_info,
groupby=groupby, orderby= sum_paid + " DESC, verified_time, reserve_time",
limitby=limitby)
sci_names = {int(r[grouped_otts]):r.reservations.name for r in curr_rows if r[n_leaves]==1} #only get sci names etc for unary sponsors
html_names = {ott:nice_species_name(sci_names[ott], vn, html=True, leaf=True, first_upper=True, break_line=2) for ott,vn in get_common_names(sci_names.keys(), return_nulls=True).items()}
otts = [int(ott) for r in curr_rows for ott in r[grouped_otts].split(",") if r[grouped_otts]]
#store the default image info (e.g. to get thumbnails, attribute correctly etc)
default_images = {r.ott:r for r in db(db.images_by_ott.ott.belongs(otts) & (db.images_by_ott.overall_best_any==1)).select(db.images_by_ott.ott, db.images_by_ott.src, db.images_by_ott.src_id, db.images_by_ott.rights, db.images_by_ott.licence, orderby=~db.images_by_ott.src)}
#also look at the nondefault images if present
user_images = {}
for r in curr_rows:
for img_src, img_src_id in zip(
(r[grouped_img_src] or '').split(","), (r[grouped_img_src_id] or '').split(",")):
if img_src is not None and img_src_id is not None:
row = db((db.images_by_ott.src == img_src) & (db.images_by_ott.src_id == img_src_id)).select(
db.images_by_ott.ott, db.images_by_ott.src, db.images_by_ott.src_id,
db.images_by_ott.rights, db.images_by_ott.licence).first()
if row:
user_images[row.ott] = row
return dict(rows=curr_rows, n_col_name=n_leaves, otts_col_name=grouped_otts, paid_col_name=sum_paid, page=page, items_per_page=items_per_page, vars=request.vars, html_names=html_names, user_images=user_images, default_images=default_images)
def sponsor_picks(sponsor_suggestion=None):
"""
Get the list of hand-picked nodes, from the sponsor_picks table. If a node has a
identifier which is a positive integer, this refers to an ott, and the node can be
displayed using the sponsor_node function. Otherwise it is a bespoke collection,
displayed using sponsor_handpicks.
The sponsor_suggestion parameter filters which picks are chosen to be returned:
* if an integer, it is one (or a combination) of the bitflags defined in
sponsor_suggestion_flags
* if it is a string, it indicates a specific sponsor_picks identifier (group name,
e.g. "trail2016" to be picked)
"""
#save 'ott':123 in pick[xxx].vars
pick = {}
sp_tab = db.sponsor_picks
query = (sp_tab.display_order is not None)
try:
query &= (sp_tab.identifier == (sponsor_suggestion+""))
except TypeError:
pass # sponsor_suggestion was a number
for row in db(query).select(orderby=sp_tab.display_order):
if (row.display_flags is None or sponsor_suggestion is None or
row.display_flags & sponsor_suggestion):
val = {v:row[v] for v in sp_tab.fields}
try:
val['vars']=loads(row.vars)
except:
val['vars']={}
if not row.thumb_url and row.thumb_src is not None:
val['thumb_url']=thumbnail_url(row.thumb_src,row.thumb_src_id)
if row.identifier.isdigit():
val['vars']['ott'] = row.identifier = int(row.identifier)
val['page'] = 'sponsor_node'
else:
val['page'] = 'sponsor_handpicks'
val['vars']['group_name'] = row.identifier
pick[row.identifier] = val
return pick
def sponsor():
return dict(pick=sponsor_picks(
sponsor_suggestion=sponsor_suggestion_flags['sponsor_by']))
def treecards():
prices = []
accumulate = 0
for row in db(db.prices).select(db.prices.ALL, orderby=db.prices.price):
accumulate += row.n_leaves
prices.append(
{'price_pounds': row.price/100, 'n': row.n_leaves, 'cumulative': accumulate})
for p in prices:
p['quantile'] = p['cumulative']/accumulate
return dict(
pick=sponsor_picks(sponsor_suggestion=sponsor_suggestion_flags['sponsor_for']),
prices=prices)
def sponsor_node_price():
"""
Pick <max> leaves for a price band, and return data for the price bands.
By default ranks by popularity. If price_pence is blank, this returns the 'contact us' details.
IMPORTANT NOTE: this query has the potential to slow down the website when someone
clicks on a node high up in the tree: a badly-formed query will end up sorting
millions of leaves according to the image ranking. For this reason, we make 2
queries, firstly for leaves with an image, ranked by image rating, and then
(if we don't get enough results returned) for leaves without an image, ranked
by popularity.
"""
price_levels_pence = sorted([row.price for row in db().select(db.prices.price)])
try:
if request.vars.get('id'):
query = sponsorable_children_query(int(request.vars.id), qtype="id")
elif request.vars.get('ott'):
query = sponsorable_children_query(int(request.vars.ott), qtype="ott")