3
from openstudio import Workshop, WorkshopsHelper, WorkshopProduct, Invoice, InvoicesHelper, OsMail, TasksHelper
5
from general_helpers import get_weekday
6
from general_helpers import get_badge
7
from general_helpers import get_label
8
from general_helpers import get_ajax_loader
9
from general_helpers import get_submenu
10
from general_helpers import get_input_search
11
from general_helpers import datestr_to_python
12
from general_helpers import class_get_teachers
13
from general_helpers import max_string_length
14
from general_helpers import Memo_links
15
from general_helpers import workshops_get_full_workshop_product_id
16
from general_helpers import classes_get_status
17
from general_helpers import set_form_id_and_get_submit_button
25
def activity_count_reservations(wsaID, fwsID):
27
Returns count of reservations for an activity
29
# count full workshop customers
30
query = (db.workshops_products_customers.workshops_products_id == fwsID) & \
31
(db.workshops_products_customers.Cancelled == False) & \
32
(db.workshops_products_customers.Waitinglist == False)
33
count_full_ws = db(query).count()
34
# count activity customers
35
query = activity_list_customers_get_list_activity_query(wsaID)
36
count_activity = db(query).count()
38
return count_full_ws + count_activity
41
def activity_get_filled(row, fwsID):
43
Formats count of reservations for use in manage page activity list
45
total = unicode(row.Spaces)
48
count = activity_count_reservations(wsaID, fwsID)
51
filled = used + "/" + total
53
filled = SPAN(filled, _class='green')
57
def get_workshops_menu(page, wsID):
60
pages = [['workshop_edit',
62
URL('workshop_edit', vars=vars)],
65
URL('products', vars=vars)],
68
URL('activities', vars=vars)],
71
URL('tasks', vars=vars)],
74
URL('stats', vars=vars)],
77
URL('info_mail', vars=vars)]]
79
return get_submenu(pages, page, horizontal=True, htype='tabs')
82
def get_subtitle(wsID):
84
Returns a subtitle for a workshop
86
workshop = db.workshops(wsID)
87
st = SPAN(workshop.Name + ' ')
89
if workshop.Startdate == workshop.Enddate:
90
st.append(SPAN(workshop.Startdate.strftime(DATE_FORMAT),
93
st.append(SPAN(workshop.Startdate.strftime(DATE_FORMAT) + ' - ' + \
94
workshop.Enddate.strftime(DATE_FORMAT),
96
elif workshop.Startdate:
97
st.append(SPAN(workshop.Startdate.strftime(DATE_FORMAT),
105
def index_get_filter_form(filter_option):
106
filter_options = [('current', T('Current')), ('archived', T('Archived'))]
108
form = FORM(_action='#',
110
_class='right select-narrow',
111
_id='workshops_show_filter')
112
select = SELECT(value=filter_option,
114
_class='generic-widget',
115
_onchange="this.form.submit();",
116
_style='vertical-align:text;')
117
for option in filter_options:
119
if filter_option == option[0]:
121
select.append(OPTION(option[1], _value=option[0], _selected=selected))
123
submit = INPUT(_type='submit', _value=T("Go"))
124
form.insert(0, select)
127
return DIV(form, _class='form_inline')
130
@auth.requires(auth.has_membership(group_id='Admins') or \
131
auth.has_permission('read', 'workshops'))
133
response.title = T("Events")
134
response.subtitle = T("")
135
response.view = 'general/only_content_no_box.html'
137
# Make sure we're not going back to workshop payments page from here
138
session.workshops_manage_back = None
140
if not session.workshops_show:
141
session.workshops_show = 'current'
143
if 'show_archive' in request.vars:
144
show = request.vars['show_archive']
145
session.workshops_show = show
147
if session.workshops_show == 'current':
148
query = (db.workshops.Archived == False)
149
elif session.workshops_show == 'archive':
150
query = (db.workshops.Archived == True)
151
response.subtitle = T("Archived")
153
if session.workshops_show == 'archive':
154
archive_class = 'active'
157
current_class = 'active'
160
if ('all' in request.vars and session.workshops_show == 'archive') or session.workshops_show == 'current':
164
# Only show initial limit for archive
166
show_all = DIV(A(T("Show all"),
167
_href=URL('workshops', 'index', vars={'all': True}),
168
_title=T('Show the full archive, might take a little while to load')))
170
delete_permission = auth.has_membership(group_id='Admins') or \
171
auth.has_permission('delete', 'workshops')
173
add_url = URL('workshop_add')
174
add = os_gui.get_button('add', add_url, T("Add a new event"))
176
archive_buttons = os_gui.get_archived_radio_buttons(
177
session.workshops_show)
179
tools = DIV(archive_buttons, add)
182
UL(LI(A(T('Current'),
183
_href=URL(vars={'show_archive': 'current'})),
184
_class=current_class),
186
_href=URL(vars={'show_archive': 'archive'})),
187
_class=archive_class),
188
# LI(I(_class='fa fa-object-group'),
189
# _class='pull-left header'),
190
_class='nav nav-tabs pull-right'),
191
DIV(DIV(index_get_content(query, limit),
193
_class='tab-pane active'),
194
_class='tab-content'),
195
_class='nav-tabs-custom')
197
return dict(content=content, add=add)
200
def index_get_content(query, limit=False):
202
Returns table with workshop content
204
delete_onclick = "return confirm('" + \
205
T('Are you sure you want to delete this workshop?') + "');"
207
header = THEAD(TR(TH(T('Image')),
216
table = TABLE(header, _class='table table-striped table-hover')
218
orderby = db.workshops.Startdate
219
if session.workshops_show == 'archive':
220
# Sort by latest first in archive
221
orderby = ~db.workshops.Startdate
225
rows = db(query).select(db.workshops.ALL, limitby=(0, limit), orderby=orderby)
227
rows = db(query).select(db.workshops.ALL, orderby=orderby)
229
for i, row in enumerate(rows):
230
repr_row = list(rows[i:i + 1].render())[0]
233
public = INPUT(value=row.PublicWorkshop,
236
_disabled='disabled')
239
edit = index_get_link_edit(row)
241
pdf = os_gui.get_button('print',
242
URL('workshops', 'pdf', vars={'wsID': row.id}),
246
archive_permission = auth.has_membership(group_id='Admins') or \
247
auth.has_permission('update', 'workshops')
248
if archive_permission:
249
archive = index_get_link_archive(row)
252
delete_permission = auth.has_membership(group_id='Admins') or \
253
auth.has_permission('delete', 'workshops')
254
if delete_permission:
255
delete = os_gui.get_button('delete_notext',
256
URL('workshop_delete', vars={'wsID': row.id}),
257
onclick=delete_onclick)
259
buttons = DIV(edit, pdf, archive, delete, _class='pull-right')
262
TD(repr_row.thumbsmall),
263
TD(max_string_length(repr_row.Name, 34),
264
_title=repr_row.Name),
265
TD(repr_row.Startdate),
266
TD(max_string_length(repr_row.auth_teacher_id, 20),
267
_title=repr_row.auth_teacher_id),
268
TD(max_string_length(repr_row.school_locations_id, 23),
269
_title=repr_row.school_locations_id),
279
def index_get_link_edit(row):
281
Returns drop down link for index edit
283
vars = {'wsID': row.id}
287
permission = (auth.has_membership(group_id='Admins') or
288
auth.has_permission('update', 'workshops'))
290
edit = A((os_gui.get_fa_icon('fa-pencil'), T('Edit')),
291
_href=URL('workshop_edit', vars=vars))
294
permission = (auth.has_membership(group_id='Admins') or
295
auth.has_permission('read', 'workshops_products'))
297
link = A((os_gui.get_fa_icon('fa-briefcase'), T('Products')),
298
_href=URL('products', vars=vars))
301
permission = (auth.has_membership(group_id='Admins') or
302
auth.has_permission('read', 'workshops_activities'))
304
link = A((os_gui.get_fa_icon('fa-clock-o'), T('Activities')),
305
_href=URL('activities', vars=vars))
308
permission = (auth.has_membership(group_id='Admins') or
309
auth.has_permission('read', 'tasks'))
311
link = A((os_gui.get_fa_icon('fa-check-square-o'), T('Tasks')),
312
_href=URL('tasks', vars=vars))
315
permission = (auth.has_membership(group_id='Admins') or
316
auth.has_permission('read', 'workshops'))
318
link = A((os_gui.get_fa_icon('fa-pie-chart'), ' ', T("Quick Stats")),
319
_href=URL('stats', vars=vars))
322
duplicate_permission = auth.has_membership(group_id='Admins') or \
323
auth.has_permission('create', 'workshops')
324
if duplicate_permission:
325
links.append(LI(_role='separator', _class='divider'))
326
duplicate_onclick = "return confirm('" + \
327
T('Are you sure you want to duplicate this workshop?') + "');"
329
link = A((os_gui.get_fa_icon('fa-clone'), ' ', T('Duplicate')),
330
_href=URL('workshop_duplicate',
331
vars={'wsID': row.id}),
332
_onclick=duplicate_onclick)
336
menu = os_gui.get_dropdown_menu(
341
menu_class='btn-group pull-left')
346
def index_get_link_archive(row):
348
Called from the index function. Changes title of archive button
349
depending on whether a workshop is archived or not
351
workshop = db.workshops(row.id)
353
if workshop.Archived:
354
tt = T("Move to current")
358
return os_gui.get_button('archive',
359
URL('workshop_archive', vars={'wsID': row.id}),
363
@auth.requires(auth.has_membership(group_id='Admins') or \
364
auth.has_permission('update', 'workshops'))
365
def workshop_archive():
367
This function archives a workshop
368
request.vars[wsID] is expected to be the workshop ID
370
wsID = request.vars['wsID']
372
session.flash = T('Unable to (un)archive workshop')
374
workshop = db.workshops(wsID)
376
if workshop.Archived:
377
session.flash = T('Moved workshop to current')
379
session.flash = T('Archived workshop')
381
workshop.Archived = not workshop.Archived
382
workshop.update_record()
385
cache_clear_workshops()
387
redirect(URL('index'))
390
def workshop_add_get_menu(page):
392
Returns submenu for adding a workshop
394
pages = [['workshop_add', T('1. Workshop'), "#"],
395
['workshop_add_set_price', T('2. Price'), "#"]]
397
return os_gui.get_submenu(pages, page, horizontal=True, htype='tabs')
400
@auth.requires_login()
403
This function shows an add page for a workshop
405
response.title = T('Add event')
406
response.subtitle = T("Event info")
407
response.view = 'general/tabs_menu.html'
409
return_url = URL('index')
410
db.workshops.Archived.readable = False
411
db.workshops.Archived.writable = False
413
workshop_hide_teacher_fields()
415
next_url = '/workshops/workshop_add_set_price?wsID=[id]'
416
crud.messages.submit_button = T("Next")
417
crud.messages.record_created = T("")
418
crud.settings.create_onaccept = [workshop_add_onaccept, cache_clear_workshops]
419
crud.settings.create_next = next_url
420
crud.settings.formstyle = 'bootstrap3_stacked'
421
form = crud.create(db.workshops)
423
textareas = form.elements('textarea')
424
for textarea in textareas:
425
textarea['_class'] += ' tmced'
427
result = set_form_id_and_get_submit_button(form, 'MainForm')
428
form = result['form']
429
submit = result['submit']
431
menu = workshop_add_get_menu(request.function)
432
back = os_gui.get_button('back', return_url)
434
return dict(content=form,
440
def workshop_add_onaccept(form):
442
Insert full workshop product after a workshop is created
445
ws_row = db.workshops(wsID)
446
db.workshops_products.insert(workshops_id=wsID,
449
Name=T('Full event'),
451
Description=T('Full event'))
454
@auth.requires_login()
455
def workshop_add_set_price():
457
Set the price of the full workshop product for a workshop
458
Uses the permissions for workshops_products
460
wsID = request.vars['wsID']
461
fwspID = workshops_get_full_workshop_product_id(wsID)
463
response.title = T('Add event')
464
response.subtitle = T("Full event price")
465
response.view = 'general/tabs_menu.html'
467
db.workshops_products.Name.readable = False
468
db.workshops_products.Name.writable = False
469
db.workshops_products.Description.readable = False
470
db.workshops_products.Description.writable = False
471
db.workshops_products.ExternalShopURL.readable = False
472
db.workshops_products.ExternalShopURL.writable = False
473
db.workshops_products.AddToCartText.readable = False
474
db.workshops_products.AddToCartText.writable = False
475
db.workshops_products.PublicProduct.readable = False
476
db.workshops_products.PublicProduct.writable = False
478
crud.messages.submit_button = T("Save")
479
crud.messages.record_updated = T("Saved")
480
crud.settings.update_deletable = False
481
crud.settings.update_next = URL('products', vars={'wsID': wsID})
482
form = crud.update(db.workshops_products, fwspID)
484
result = set_form_id_and_get_submit_button(form, 'MainForm')
485
form = result['form']
486
submit = result['submit']
488
menu = workshop_add_get_menu(request.function)
490
return dict(content=form,
495
@auth.requires(auth.has_membership(group_id='Admins') or
496
auth.has_permission('delete', 'workshops'))
497
def workshop_delete():
501
wsID = request.vars['wsID']
503
query = (db.workshops.id == wsID)
506
session.flash = T('Workshop deleted')
509
cache_clear_workshops()
511
redirect(URL('index'))
514
@auth.requires_login()
517
This function shows an edit page for a workshop
518
request.args[0] is expected to be the workshop ID (wsID)
520
wsID = request.vars['wsID']
521
return_url = URL('index')
523
response.title = T("Edit event")
524
response.subtitle = get_subtitle(wsID)
525
response.view = 'general/tabs_menu.html'
527
workshop_hide_teacher_fields()
529
crud.messages.submit_button = T("Save")
530
crud.messages.record_updated = T("Saved")
531
crud.settings.update_next = URL(vars={'wsID': wsID})
532
crud.settings.update_onaccept = [cache_clear_workshops]
533
crud.settings.update_deletable = False
534
crud.settings.formstyle = 'bootstrap3_stacked'
535
form = crud.update(db.workshops, wsID)
538
form_element = form.element('form')
539
form['_id'] = form_id
541
elements = form.elements('input, select, textarea')
542
for element in elements:
543
element['_form'] = form_id
545
submit = form.element('input[type=submit]')
547
textareas = form.elements('textarea')
548
for textarea in textareas:
549
textarea['_class'] += ' tmced'
551
menu = get_workshops_menu(request.function, wsID)
552
back = os_gui.get_button('back', return_url)
553
content = DIV(workshop_get_alert_no_activities(wsID),
556
return dict(content=content,
562
@auth.requires_login()
563
def workshop_edit_teachers():
565
Edit teachers for a workshop
566
request.vars['wsID'] is expected to be the workshops_id
567
request.vars['wiz'] is expected to be 'True' in case the wizzard is
570
wsID = request.vars['wsID']
571
wizzard = True if 'wiz' in request.vars else False
572
return_url = URL('index')
573
response.title = T("Edit workshop")
574
response.subtitle = get_subtitle(wsID)
576
response.view = 'general/tabs_menu.html'
579
# call js for styling the form
580
crud.messages.submit_button = T("Next")
581
crud.messages.record_updated = T("")
582
crud.settings.update_next = URL('workshops',
583
'workshop_add_set_price',
586
menu = workshop_add_get_menu(request.function)
587
response.title = T('Add workshop')
588
response.subtitle = T("Teachers")
591
crud.messages.submit_button = T("Save")
592
crud.messages.record_updated = T("Saved teachers")
593
# crud.settings.update_next = return_url
594
back = os_gui.get_button('back', return_url)
595
menu = workshop_edit_get_submenu(request.function, wsID)
597
db.workshops.Name.readable = False
598
db.workshops.Name.writable = False
599
db.workshops.Startdate.readable = False
600
db.workshops.Startdate.writable = False
601
db.workshops.Enddate.readable = False
602
db.workshops.Enddate.writable = False
603
db.workshops.picture.readable = False
604
db.workshops.picture.writable = False
605
db.workshops.PublicWorkshop.readable = False
606
db.workshops.PublicWorkshop.writable = False
607
db.workshops.Description.readable = False
608
db.workshops.Description.writable = False
609
db.workshops.school_locations_id.readable = False
610
db.workshops.school_locations_id.writable = False
612
crud.settings.update_onaccept = [cache_clear_workshops]
613
crud.settings.update_deletable = False
614
form = crud.update(db.workshops, wsID)
616
result = set_form_id_and_get_submit_button(form, 'MainForm')
617
form = result['form']
618
submit = result['submit']
620
return dict(content=form,
626
def workshop_hide_teacher_fields(var=None):
628
Sets readable and writable to False for teacher fields in
631
db.workshops.Teacher.readable = False
632
db.workshops.Teacher.writable = False
633
db.workshops.TeacherEmail.readable = False
634
db.workshops.TeacherEmail.writable = False
635
db.workshops.Teacher2.readable = False
636
db.workshops.Teacher2.writable = False
637
db.workshops.Teacher2Email.readable = False
638
db.workshops.Teacher2Email.writable = False
639
db.workshops.Startdate.readable = False
640
db.workshops.Startdate.writable = False
643
@auth.requires(auth.has_membership(group_id='Admins') or \
644
auth.has_permission('create', 'workshops'))
645
def workshop_duplicate():
647
Duplicate a workshop including products & activities
649
wsID = request.vars['wsID']
651
workshop = db.workshops(wsID)
652
new_wsID = db.workshops.insert(
653
Archived=workshop.Archived,
654
PublicWorkshop=False,
655
Name=workshop.Name + ' ' + T('(Copy)'),
656
Startdate=workshop.Startdate,
657
Enddate=workshop.Enddate,
658
Starttime=workshop.Starttime,
659
Endtime=workshop.Endtime,
660
auth_teacher_id=workshop.auth_teacher_id,
661
auth_teacher_id2=workshop.auth_teacher_id2,
662
Description=workshop.Description,
663
school_locations_id=workshop.school_locations_id,
664
picture=workshop.picture,
665
thumbsmall=workshop.thumbsmall,
666
thumblarge=workshop.thumblarge,
670
activity_ids_old = []
671
activity_ids_map = []
672
query = (db.workshops_activities.workshops_id == wsID)
673
rows = db(query).select(db.workshops_activities.ALL)
675
new_wsaID = db.workshops_activities.insert(
676
workshops_id=new_wsID,
677
Activity=row.Activity,
678
Activitydate=row.Activitydate,
679
school_locations_id=row.school_locations_id,
680
Starttime=row.Starttime,
683
auth_teacher_id=row.auth_teacher_id,
684
auth_teacher_id2=row.auth_teacher_id2,
686
activity_ids_old.append(row.id)
687
activity_ids_map.append({'old': row.id,
691
products_ids_old = []
692
products_ids_map = []
693
query = (db.workshops_products.workshops_id == wsID)
694
rows = db(query).select(db.workshops_products.ALL)
696
new_wspID = db.workshops_products.insert(
697
workshops_id=new_wsID,
698
FullWorkshop=row.FullWorkshop,
699
Deletable=row.Deletable,
700
PublicProduct=row.PublicProduct,
703
tax_rates_id=row.tax_rates_id,
704
Description=row.Description,
705
ExternalShopURL=row.ExternalShopURL,
706
AddToCartText=row.AddToCartText,
707
Donation=row.Donation
709
products_ids_old.append(row.id)
710
products_ids_map.append({'old': row.id,
713
# products activities
714
query = db.workshops_products_activities.workshops_products_id.belongs(products_ids_old)
715
rows = db(query).select(db.workshops_products_activities.ALL)
717
for pim in products_ids_map:
718
if pim['old'] == row.workshops_products_id:
719
new_wspID = pim['new']
721
for aim in activity_ids_map:
722
if aim['old'] == row.workshops_activities_id:
723
new_wsaID = aim['new']
725
db.workshops_products_activities.insert(
726
workshops_products_id=new_wspID,
727
workshops_activities_id=new_wsaID
731
cache_clear_workshops()
733
session.flash = T('You are now editing the duplicated workshop')
735
redirect(URL('workshop_edit', vars={'wsID': new_wsID}))
738
def activity_add_edit_update_workshop_datetime_info(form):
740
:param form: db.workshops_activities add or edit form
741
Set dates & times for a workshop based on activities
743
activity = db.workshops_activities(form.vars.id)
744
workshop = Workshop(activity.workshops_id)
746
workshop.update_dates_times()
749
@auth.requires_login()
752
This function shows an add page for a workshop activity
754
wsID = request.args[0]
755
response.title = T('Event')
756
response.subtitle = get_subtitle(wsID)
757
response.view = 'general/tabs_menu.html'
759
workshop = db.workshops(wsID)
761
db.workshops_activities.workshops_id.default = wsID
762
db.workshops_activities.Teacher.default = workshop.Teacher
763
db.workshops_activities.Teacher2.default = workshop.Teacher2
764
db.workshops_activities.auth_teacher_id.default = workshop.auth_teacher_id
765
db.workshops_activities.auth_teacher_id2.default = workshop.auth_teacher_id2
767
return_url = URL('activities', vars={'wsID': wsID})
769
crud.messages.submit_button = T("Save")
770
crud.messages.record_created = T("Added activity")
771
crud.settings.create_onaccept = [activity_add_edit_update_workshop_datetime_info, cache_clear_workshops]
772
crud.settings.create_next = return_url
773
crud.settings.formstyle = 'table3cols'
774
form = crud.create(db.workshops_activities)
777
form_element = form.element('form')
778
form['_id'] = form_id
780
elements = form.elements('input, select, textarea')
781
for element in elements:
782
element['_form'] = form_id
784
submit = form.element('input[type=submit]')
786
content = DIV(H4(T('New activity')), BR(), form)
788
back = os_gui.get_button('back', return_url)
789
menu = get_workshops_menu('activities', wsID)
791
return dict(content=content, back=back, menu=menu, save=submit)
794
@auth.requires_login()
797
This function shows an edit page for an activity
798
request.args[0] is expected to be the workshops_activities_id (wsaID)
800
wsaID = request.args[0]
801
response.title = T('Event')
802
wsID = db.workshops_activities(wsaID).workshops_id
803
response.subtitle = get_subtitle(wsID)
804
response.view = 'general/tabs_menu.html'
806
return_url = URL('activities', vars={'wsID': wsID})
808
crud.messages.submit_button = T("Save")
809
crud.messages.record_updated = T("Saved")
810
crud.messages.record_deleted = T("Deleted activity")
811
crud.settings.update_onaccept = [activity_add_edit_update_workshop_datetime_info, cache_clear_workshops]
812
crud.settings.update_next = return_url
813
crud.settings.update_deletable = False
815
form = crud.update(db.workshops_activities, wsaID)
818
form_element = form.element('form')
819
form['_id'] = form_id
821
elements = form.elements('input, select, textarea')
822
for element in elements:
823
element['_form'] = form_id
825
submit = form.element('input[type=submit]')
827
back = os_gui.get_button('back', return_url)
828
content = DIV(H4(T('Edit activity')), BR(), form)
830
menu = get_workshops_menu('activities', wsID)
832
return dict(content=content, back=back, menu=menu, save=submit)
835
@auth.requires(auth.has_membership(group_id='Admins') or \
836
auth.has_permission('delete', 'workshops_activities'))
837
def activity_delete():
839
Delete the activity specified by request.args[1] (wsaID)
841
wsID = request.args[0]
842
wsaID = request.args[1]
845
query = (db.workshops_activities.id == wsaID)
848
# Update dates & times for workshop
849
workshop = Workshop(wsID)
850
workshop.update_dates_times()
853
cache_clear_workshops()
855
redirect(URL('activities', vars={'wsID': wsID}))
858
@auth.requires(auth.has_membership(group_id='Admins') or \
859
auth.has_permission('read', 'workshops'))
860
def mail_activity_attendance():
862
This function sends the attendance list for all activities
863
to the workshop teacher
865
wsID = request.vars['wsID']
867
workshop = db.workshops(wsID)
870
message = "<html>\n<head><STYLE TYPE='text/css'> \
871
<!-- TD{font-size: 9pt; align:left;} \
872
TD, TH { padding:0 20px 0 0; } \
873
TH{font-size: 9pt; text-align:left;} ---> </STYLE></head> \
874
<body style=font-size:10pt; font-style:normal;'>"
875
message += T("Dear teacher(s)")
876
message += ",<br><br>"
877
message += 'Below are the attendance lists for the activities in your '
878
message += 'workshop, ' + workshop.Name + '.<br><br>'
880
fws_rows = activity_list_customers_get_fullws_rows(wsID)
882
query = (db.workshops_activities.workshops_id == wsID)
883
rows = db(query).select(db.workshops_activities.ALL,
884
orderby=db.workshops_activities.Activitydate | \
885
db.workshops_activities.Starttime)
888
subtitle = T("On ") + row.Activitydate.strftime(DATE_FORMAT) + \
890
row.Starttime.strftime(TIME_FORMAT)
891
message += unicode(H3(title))
892
message += unicode(subtitle)
893
message += '<br><br>'
895
for fws_row in fws_rows.render():
896
name = fws_row.auth_user.display_name
897
table.append(TR(TD(name),
898
TD(unicode(T('Full event')))))
899
wsa_rows = activity_list_customers_get_activity_rows(row.id)
900
for wsa_row in wsa_rows:
901
name = wsa_row.auth_user.display_name,
902
table.append(TR(TD(name), TD()))
904
message += unicode(table)
907
subject = T("Reservations for") + ' ' + workshop.Name + ', ' + \
908
T("starting on") + ' ' + \
909
workshop.Startdate.strftime(DATE_FORMAT) + '.'
911
if db.sys_properties(Property='smtp_signature'):
912
smtp_signature = db.sys_properties(Property='smtp_signature').PropertyValue
915
message += smtp_signature
916
message += "<br><br><br><font size=2>" + T("This message is autogenerated by OpenStudio")
917
message += " - <a href='http://www.openstudioproject.com'>www.openstudioproject.com</a></font>"
918
message += "</body></html>"
922
if workshop.TeacherEmail:
923
to.append(workshop.TeacherEmail.strip())
924
if workshop.Teacher2Email:
925
to.append(workshop.Teacher2Email.strip())
926
if workshop.auth_teacher_id:
927
teacher = db.auth_user(workshop.auth_teacher_id)
928
to.append(teacher.email.strip())
929
if workshop.auth_teacher_id2:
930
teacher = db.auth_user(workshop.auth_teacher_id2)
931
to.append(teacher.email.strip())
934
session.flash = T("Please check the teachers' email address(es).")
939
# If reply_to is omitted, then mail.settings.sender is used
944
session.flash = T("Successfully sent mail")
946
session.flash = T("Unable to send mail")
948
redirect(URL('activities', vars={'wsID': wsID}))
951
@auth.requires(auth.has_membership(group_id='Admins') or \
952
auth.has_permission('update', 'workshops_activities'))
953
def attendance_delete():
955
This function removed the specified attendance record
956
request.vars['id'] is expected to be the
957
workshops_reservation id (wsr_id)
959
response.view = 'generic.json'
960
wsr_id = request.vars['id']
962
query = (db.workshops_reservation.id == wsr_id)
963
result = db(query).delete()
965
# result > 0 means something was removed
967
message = T("Successfully removed")
970
message = T("Uh oh... something went wrong...")
973
return dict(message=message, status=status)
976
@auth.requires(auth.has_membership(group_id='Admins') or \
977
auth.has_permission('update', 'workshops_activities'))
978
def activity_update_attendance():
981
This function is meant to be called with the json extension.
982
It takes id and Info as variables.
984
if not request.extension == 'json':
985
return T("Error, please call as JSON")
987
response.view = 'generic.json'
991
cuID = request.vars['cuID']
992
wsaID = request.vars['wsaID']
995
if 'attending' in request.vars:
996
result = db.workshops_activities_customers.insert(
997
auth_customer_id=cuID,
998
workshops_activities_id=wsaID)
1001
(db.workshops_activities_customers.auth_customer_id == cuID) & \
1002
(db.workshops_activities_customers.workshops_activities_id == wsaID)
1003
result = db(query).delete()
1007
message = T("Updated attendance")
1009
message = T("Uh oh... something went wrong...")
1011
message = T("Error: no data received")
1013
return dict(status=status, message=message)
1016
@auth.requires(auth.has_membership(group_id='Admins') or \
1017
auth.has_permission('update', 'workshops_activities'))
1018
def activity_duplicate():
1020
This function duplicates an activity and
1021
redirects to the duplicated activity's edit page
1023
wsaID = request.args[0]
1024
row = db.workshops_activities[wsaID]
1026
id = db.workshops_activities.insert(
1027
workshops_id=row.workshops_id,
1028
Activity=row.Activity + u' (Copy)',
1029
Activitydate=row.Activitydate,
1030
school_locations_id=row.school_locations_id,
1031
auth_teacher_id=row.auth_teacher_id,
1032
auth_teacher_id2=row.auth_teacher_id2,
1033
Starttime=row.Starttime,
1034
Endtime=row.Endtime,
1038
cache_clear_workshops()
1040
session.flash = T("You are now editing the duplicated activity")
1042
redirect(URL('activity_edit', args=[id]))
1045
@auth.requires(auth.has_membership(group_id='Admins') or \
1046
auth.has_permission('read', 'workshops'))
1049
This function shows a page with a quick view of the revenue and
1050
a table listing the top 10 cities of attending customers.
1051
request.vars['wsID'] is expected to be the workshopID
1053
wsID = request.vars['wsID']
1054
workshop = db.workshops(wsID)
1055
response.title = T('Event')
1056
response.subtitle = get_subtitle(wsID)
1057
response.view = 'general/tabs_menu.html'
1059
### top 10 cities begin
1060
cities = DIV(stats_get_top10cities(wsID),
1063
### top 10 cities end
1065
### Revenue begin ###
1066
revenue = DIV(stats_get_revenue(wsID), _class='col-md-6')
1070
content = DIV(DIV(revenue, cities,
1071
_class='col-md-12'),
1074
menu = get_workshops_menu(request.function, wsID)
1075
back = manage_get_back()
1077
return dict(content=content,
1080
left_sidebar_enabled=True)
1083
def stats_get_top10cities(wsID):
1085
Returns overview of top10 cities for a workshop
1087
count = db.auth_user.city.count()
1088
query = (db.workshops_products.workshops_id == wsID) & \
1089
(db.workshops_products_customers.workshops_products_id ==
1090
db.workshops_products.id) & \
1091
(db.workshops_products_customers.auth_customer_id ==
1092
db.auth_user.id) & \
1093
((db.auth_user.city != None) & (db.auth_user.city != ''))
1095
rows = db(query).select(db.auth_user.city, count,
1096
groupby=db.auth_user.city,
1097
orderby=~count | db.auth_user.city, limitby=(0, 10))
1101
l.append([row.auth_user.city, row[count]])
1103
title = T("Top 10 cities")
1104
table = TABLE(TR(TH(T("City")), TH(T("Customers"))), _class='table')
1106
table.append(TR(*item))
1108
panel = os_gui.get_panel_table(title, table)
1113
def stats_get_revenue(wsID):
1115
Returns revenue of a workshop, specified by product
1118
table = TABLE(TR(TH(T("Product")),
1120
TH(SPAN(T('Price'), _class='pull-right')),
1121
TH(SPAN(T('Total'), _class='pull-right'))),
1124
# first get all products
1125
query = (db.workshops_products.workshops_id == wsID)
1126
rows = db(query).select(db.workshops_products.ALL,
1127
orderby=~db.workshops_products.FullWorkshop | \
1128
db.workshops_products.Name)
1130
# get nr of sold products
1131
query = (db.workshops_products_customers.workshops_products_id == row.id)
1132
count = db(query).count()
1135
total = count * (row.Price or 0)
1136
total_revenue += total
1140
table.append(TR(TD(row.Name),
1142
TD(SPAN(CURRSYM, ' ',
1143
row.Price or '', _class='pull-right')),
1144
TD(SPAN(CURRSYM, ' ',
1145
total, _class='pull-right'))))
1147
table.append(TR(TD(T("Total")),
1150
TD(SPAN(CURRSYM, ' ',
1151
format(total_revenue, '.2f'), _class='pull-right')),
1153
title = T("Revenue")
1155
panel = os_gui.get_panel_table(title, table)
1160
def manage_get_back(var=None):
1162
Generate back button for workshops manage pages
1164
if session.workshops_manage_back == 'default_workshoppayments_open':
1165
url = URL('default', 'workshop_payments_open')
1166
elif session.workshops_manage_back == 'pinboard':
1167
url = URL('pinboard', 'index')
1168
elif session.workshops_manage_back == 'tasks_index':
1169
url = URL('tasks', 'index')
1173
return os_gui.get_button('back', url)
1176
@auth.requires(auth.has_membership(group_id='Admins') or \
1177
auth.has_permission('read', 'workshops_products'))
1180
Lists products for a workshop
1181
request.vars['wsID'] is expected to be the workshops_id
1183
wsID = request.vars['wsID']
1184
response.title = T('Event')
1185
response.subtitle = get_subtitle(wsID)
1186
response.view = 'workshops/manage.html'
1188
## Products begin ##
1190
products.append(workshop_get_alert_no_activities(wsID))
1193
perm = auth.has_membership(group_id='Admins') or \
1194
auth.has_permission('create', 'workshops_products')
1196
add_url = URL('product_add', args=[wsID])
1197
add = os_gui.get_button('add', add_url, T("Add new product"), _class='pull-right')
1199
table = TABLE(THEAD(TR(TH(T('Name')),
1200
TH(T('Description')),
1206
_class='table table-hover')
1208
query = (db.workshops_products.workshops_id == wsID)
1209
rows = db(query).select(db.workshops_products.ALL)
1210
for row in rows.render():
1217
shop = INPUT(_type="checkbox",
1218
_disabled="disabled")
1219
if row.PublicProduct:
1220
shop['_checked'] = "checked"
1222
if row.FullWorkshop:
1223
fws_label = get_fullws_label()
1227
# check permission for adding activities to products
1228
perm = auth.has_membership(group_id='Admins') or \
1229
auth.has_permission('update', 'workshops_products_activities')
1230
if perm and not row.FullWorkshop:
1231
activities = os_gui.get_button('list_notext',
1232
URL('product_activities',
1235
tooltip=T("Activities"))
1237
# check permission to update workshops (edit and delete)
1238
perm = auth.has_membership(group_id='Admins') or \
1239
auth.has_permission('update', 'workshops')
1241
edit = os_gui.get_button('edit_notext',
1242
URL('product_edit', args=[row.id]),
1243
tooltip=T('Edit product'))
1245
# check permission to create workshop products
1246
perm = auth.has_membership(group_id='Admins') or \
1247
auth.has_permission('create', 'workshops_products')
1249
duplicate = os_gui.get_button('duplicate',
1250
URL('product_duplicate', vars={'wspID':row.id}),
1251
tooltip=T("Duplicate product"))
1253
# check delete permission
1255
confirm_msg = T("Really remove this product?")
1256
onclick = "return confirm('" + confirm_msg + "');"
1258
if auth.has_membership(group_id='Admins') or \
1259
auth.has_permission('delete', 'workshops_products'):
1260
delete = os_gui.get_button('delete_notext',
1261
URL('product_delete', vars={'wsID': wsID,
1264
tooltip=T('Delete product'),
1265
_class='pull-right')
1267
# check permission to view customers for a product
1268
perm = auth.has_membership(group_id='Admins') or \
1269
auth.has_permission('read', 'workshops_products_customers')
1271
customers = os_gui.get_button(
1273
URL('products_list_customers', vars={'wsID': wsID,
1275
tooltip=T('Customers')
1280
buttons = DIV(delete,
1285
_class='btn-group pull-right'))
1287
table.append(TR(TD(row.Name),
1288
TD(max_string_length(row.Description, 40)),
1294
products.append(table)
1298
export = products_get_export(wsID)
1302
menu = get_workshops_menu(request.function, wsID)
1303
back = manage_get_back()
1305
return dict(content=content,
1308
header_tools=export,
1312
def products_get_export(wsID):
1314
Returns export drop down for schedule
1316
mailinglist = A(os_gui.get_fa_icon('fa-envelope-o'), T("Mailing list"),
1317
_href=URL('products_export_excel',
1318
vars={'wsID': wsID, 'export_type':'mailinglist'}))
1319
attendancelist = A(os_gui.get_fa_icon('fa-check-square-o'), T("Attendance list"),
1320
_href=URL('products_export_excel',
1321
vars={'wsID': wsID, 'export_type':'attendancelist'}))
1323
links = [mailinglist, attendancelist]
1325
export = os_gui.get_dropdown_menu(
1329
btn_icon='download',
1330
menu_class='pull-right')
1335
@auth.requires(auth.has_membership(group_id='Admins') or \
1336
auth.has_permission('read', 'workshops_products'))
1337
def products_export_excel():
1339
Export mailinglist for a workshop product.
1340
Have one worksheet for each product and one for all products
1342
def add_product_sheet(wspID):
1343
wsp = WorkshopProduct(wspID)
1345
ws = wb.create_sheet(title=wsp.name)
1347
left = [ db.auth_user.on(db.auth_user.id == db.workshops_products_customers.auth_customer_id),
1348
db.workshops_products.on(
1349
db.workshops_products_customers.workshops_products_id == db.workshops_products.id),
1350
db.invoices_workshops_products_customers.on(
1351
db.invoices_workshops_products_customers.workshops_products_customers_id ==
1352
db.workshops_products_customers.id),
1353
db.invoices.on(db.invoices_workshops_products_customers.invoices_id == db.invoices.id)
1355
query = ((db.workshops_products_customers.workshops_products_id == wspID) &
1356
(db.workshops_products_customers.Cancelled == False))
1357
rows = db(query).select(db.auth_user.first_name,
1358
db.auth_user.last_name,
1360
db.invoices.InvoiceID,
1361
db.workshops_products.Name,
1366
if export_type == 'mailinglist':
1367
ws.append([row.auth_user.first_name,
1368
row.auth_user.last_name,
1369
row.auth_user.email])
1370
elif export_type == 'attendancelist':
1371
ws.append([row.auth_user.first_name,
1372
row.auth_user.last_name,
1373
row.auth_user.email,
1374
row.invoices.InvoiceID,
1375
row.workshops_products.Name])
1377
wsID = request.vars['wsID']
1378
export_type = request.vars['export_type']
1379
workshop = Workshop(wsID)
1382
stream = cStringIO.StringIO()
1384
wb = openpyxl.workbook.Workbook(write_only=True)
1385
# write the sheet for all mail addresses
1386
ws = wb.create_sheet(title="All products")
1387
# get all products for a workshop
1389
products = workshop.get_products()
1391
for product in products:
1392
product_ids.append(product.id)
1395
left = [ db.auth_user.on(db.auth_user.id == db.workshops_products_customers.auth_customer_id),
1396
db.workshops_products.on(db.workshops_products_customers.workshops_products_id == db.workshops_products.id),
1397
db.invoices_workshops_products_customers.on(
1398
db.invoices_workshops_products_customers.workshops_products_customers_id ==
1399
db.workshops_products_customers.id),
1400
db.invoices.on(db.invoices_workshops_products_customers.invoices_id == db.invoices.id),
1402
query = ((db.workshops_products_customers.workshops_products_id.belongs(product_ids)) &
1403
(db.workshops_products_customers.Cancelled == False))
1404
rows = db(query).select(db.auth_user.first_name,
1405
db.auth_user.last_name,
1407
db.invoices.InvoiceID,
1408
db.workshops_products.Name,
1414
# distinct isn't working here in w2p 2.15.4, this is a workaround
1416
if row.auth_user.email not in emails:
1417
if export_type == 'mailinglist':
1418
ws.append([row.auth_user.first_name,
1419
row.auth_user.last_name,
1420
row.auth_user.email])
1421
elif export_type == 'attendancelist':
1422
ws.append([row.auth_user.first_name,
1423
row.auth_user.last_name,
1424
row.auth_user.email,
1425
row.invoices.InvoiceID,
1426
row.workshops_products.Name])
1427
emails.append(row.auth_user.email)
1430
for wspID in product_ids:
1431
add_product_sheet(wspID)
1434
if export_type == 'mailinglist':
1435
fname = T("Mailinglist") + '.xlsx'
1436
elif export_type == 'attendancelist':
1437
fname = T('AttendanceList') + '.xlsx'
1440
response.headers['Content-Type'] = 'application/vnd.ms-excel'
1441
response.headers['Content-disposition'] = 'attachment; filename=' + fname
1443
return stream.getvalue()
1446
@auth.requires(auth.has_membership(group_id='Admins') or \
1447
auth.has_permission('create', 'workshops_products'))
1448
def product_duplicate():
1450
Duplicate workshop product & linked activities
1452
wspID = request.vars['wspID']
1454
wsp = db.workshops_products(wspID)
1456
new_wspID = db.workshops_products.insert(
1457
workshops_id = wsp.workshops_id,
1458
FullWorkshop = False,
1460
PublicProduct = False,
1461
Name = wsp.Name + ' (Copy)',
1463
tax_rates_id = wsp.tax_rates_id,
1464
Description = wsp.Description,
1465
ExternalShopURL = wsp.ExternalShopURL,
1466
AddToCartText = wsp.AddToCartText,
1467
Donation = wsp.Donation
1470
if wsp.FullWorkshop:
1471
query = (db.workshops_activities.workshops_id == wsp.workshops_id)
1472
rows = db(query).select(db.workshops_activities.id)
1474
db.workshops_products_activities.insert(
1475
workshops_products_id=new_wspID,
1476
workshops_activities_id=row.id
1479
query = (db.workshops_products_activities.workshops_products_id == wspID)
1480
rows = db(query).select(db.workshops_products_activities.ALL)
1482
db.workshops_products_activities.insert(
1483
workshops_products_id = new_wspID,
1484
workshops_activities_id = row.workshops_activities_id
1487
session.flash = T('You are now editing the duplicated product')
1489
redirect(URL('workshops', 'product_edit', args=new_wspID))
1492
def workshop_get_alert_no_activities(wsID):
1494
Display an information banner to notify user when no activities have been found
1498
query = (db.workshops_activities.workshops_id == wsID)
1499
count = db(query).count()
1501
add = os_gui.get_button('add', URL('activity_add', args=wsID),
1502
btn_class='btn-link',
1503
title=T('Add activity'))
1504
alert = os_gui.get_alert('info', SPAN(
1505
SPAN(T('No activities have been found for this event'), _class="bold"), ' ',
1511
@auth.requires(auth.has_membership(group_id='Admins') or \
1512
auth.has_permission('read', 'workshops_activities'))
1515
Manage agenda for workshops
1517
wsID = request.vars['wsID']
1518
response.title = T('Event')
1519
response.subtitle = get_subtitle(wsID)
1520
response.view = 'workshops/manage.html'
1529
perm = auth.has_membership(group_id='Admins') or \
1530
auth.has_permission('create', 'workshops_activities')
1532
add_url = URL('activity_add', args=[wsID])
1533
add = os_gui.get_button('add', add_url, T("Add new activity"), _class='pull-right')
1535
agenda_items = TABLE(THEAD(TR(TH(T("Date")),
1542
_class='table table-hover')
1543
query = (db.workshops_activities.workshops_id == wsID)
1544
rows = db(query).select(db.workshops_activities.ALL,
1545
orderby=db.workshops_activities.Activitydate | \
1546
db.workshops_activities.Starttime)
1547
for row in rows.render():
1550
internal_location = not row.school_locations_id is None and \
1551
not row.school_locations_id == ''
1552
if internal_location:
1553
location = row.school_locations_id
1555
location = row.LocationExternal
1557
fwsID = workshops_get_full_workshop_product_id(wsID)
1558
activity = TR(TD(row.Activitydate),
1559
TD(row.Starttime, ' - ', row.Endtime),
1562
TD(row.auth_teacher_id),
1563
TD(activity_get_filled(row, fwsID)),
1566
# check permissions to manage customers attendance
1569
perm = auth.has_membership(group_id='Admins') or \
1570
auth.has_permission('update', 'workshops_activities_customers')
1572
modal_title = row.Activity + ' ' + \
1573
row.Activitydate + ' ' + \
1575
load_content = os_gui.get_ajax_loader(message=T("Loading..."))
1576
modal_content = LOAD('workshops', 'activity_list_customers.load',
1578
content=load_content,
1579
vars={'wsID': wsID, 'wsaID': row.id})
1581
customers_button_id = 'wsa_cust_' + unicode(row.id)
1582
btn_icon = SPAN(_class='glyphicon glyphicon-user')
1584
result = os_gui.get_modal(button_text=XML(btn_icon),
1585
modal_title=modal_title,
1586
modal_content=modal_content,
1587
modal_class=customers_button_id,
1589
button_class='btn-sm workshops_show_customers',
1590
button_id=customers_button_id)
1591
modals.append(result['modal'])
1592
customers = result['button']
1594
# check permission to edit activities
1599
perm = auth.has_membership(group_id='Admins') or \
1600
auth.has_permission('update', 'workshops_activities')
1602
edit = os_gui.get_button('edit_notext',
1603
URL('activity_edit', args=[wsaID]),
1604
tooltip=T("Edit activity"))
1606
duplicate = os_gui.get_button('duplicate',
1607
URL('activity_duplicate', args=[wsaID]),
1608
tooltip=T("Duplicate activity"))
1610
if auth.has_membership(group_id='Admins') or \
1611
auth.has_permission('delete', 'workshops_activities'):
1612
confirm_msg = T("Are you sure you want to delete this activity?")
1613
delete = os_gui.get_button('delete_notext',
1614
URL('activity_delete', args=[wsID, wsaID]),
1615
tooltip=T('Delete activity'),
1616
onclick="return confirm('" + confirm_msg + "');")
1618
buttons = DIV(customers,
1622
_class='btn-group pull-right')
1623
activity.append(TD(buttons))
1625
agenda_items.append(activity)
1627
agenda.append(agenda_items)
1629
permission = auth.has_membership(group_id='Admins') or \
1630
auth.has_permission('update', 'class_status')
1632
oc_count = overlapping_classes_get_count_all(wsID)
1634
badge_type = 'success'
1636
badge_type = 'default'
1637
oc_badge = get_badge(badge_type, oc_count)
1638
oc = DIV(A(T("Overlapping classes"), ' ', oc_badge,
1639
_href=URL('overlapping_classes', vars={'wsID': wsID}),
1640
_class='btn btn-primary btn-sm'),
1641
_class='pull-right ')
1643
mail = A(SPAN(_class='glyphicon glyphicon-envelope'), ' ',
1644
T("Mail attendance lists to teacher"),
1645
_href=URL('mail_activity_attendance', vars={'wsID': wsID}),
1646
_class='btn btn-primary btn-sm')
1651
export_activities = A(SPAN(os_gui.get_fa_icon('fa-check'), ' ',
1653
_href=URL('activities_export_attendance',
1654
vars={'wsID': wsID}))
1656
links = [export_activities]
1658
export = os_gui.get_dropdown_menu(
1662
btn_icon='download',
1663
menu_class='pull-right')
1665
content = DIV(agenda, modals)
1667
menu = get_workshops_menu(request.function, wsID)
1668
back = manage_get_back()
1670
return dict(content=content,
1673
header_tools=export,
1677
@auth.requires(auth.has_membership(group_id='Admins') or \
1678
auth.has_permission('read', 'workshops_activities'))
1679
def activities_export_attendance():
1681
Create excel export of workshop attendance
1682
request.vars['wsID'] is expected to be workshops.id
1684
wsID = request.vars['wsID']
1685
workshop = db.workshops(wsID)
1687
query = (db.workshops_activities.workshops_id == wsID)
1688
rows = db(query).select(db.workshops_activities.id,
1689
orderby=db.workshops_activities.Activitydate)
1692
wsa_ids.append(row.id)
1694
fw_rows = activity_list_customers_get_fullws_rows(wsID)
1697
stream = cStringIO.StringIO()
1699
# Create the workbook
1701
wb = openpyxl.workbook.Workbook(write_only=True)
1703
# process activities
1704
for wsaID in wsa_ids:
1705
activity = db.workshops_activities(wsaID)
1706
title = activity.Activity[0:30]
1707
date = activity.Activitydate.strftime(DATE_FORMAT)
1708
start = activity.Starttime.strftime(TIME_FORMAT)
1709
ws = wb.create_sheet(title=title)
1710
desc = [workshop.Name + ' ' + date + ' ' + title + ' ' + start]
1713
header = ['Customer', 'Product', 'Attending']
1716
# add full workshop customers
1717
for row in fw_rows.render():
1718
cuID = row.auth_user.id
1722
check = db.workshops_activities_customers(
1723
auth_customer_id=cuID,
1724
workshops_activities_id=wsaID)
1728
custname = row.auth_user.display_name
1729
excel_row = [custname, 'Full event', attending]
1730
ws.append(excel_row)
1732
# add customers with another product
1733
a_rows = activity_list_customers_get_activity_rows(wsaID)
1734
for row in a_rows.render():
1735
cuID = row.auth_user.id
1736
product = row.workshops_products_customers.workshops_products_id
1740
check = db.workshops_activities_customers(auth_customer_id=cuID,
1741
workshops_activities_id=wsaID)
1745
custname = row.auth_user.display_name
1746
excel_row = [custname, product, attending]
1747
ws.append(excel_row)
1750
fname = 'Attendance.xlsx'
1751
response.headers['Content-Type'] = 'application/vnd.ms-excel'
1752
response.headers['Content-disposition'] = 'attachment; filename=' + fname
1754
return stream.getvalue()
1757
@auth.requires(auth.has_membership(group_id='Admins') or \
1758
auth.has_permission('update', 'workshops_activities'))
1759
def activity_list_customers():
1761
List customers for an activity
1762
request.args[0] is expected to be the workshops_id (wsID)
1763
request.args[1] is expected to be the workshops_activities_id (wsaID)
1765
wsID = request.vars['wsID']
1766
wsaID = request.vars['wsaID']
1768
# get activity attendance
1769
table = activity_list_customers_get_list(wsID, wsaID)
1771
return dict(content=table)
1774
def activity_list_customers_get_list(wsID,
1777
Lists customers for activity
1780
def process_rows(table, rows, fullWS=False):
1782
Helper to add rows to table
1784
for row in rows.render():
1786
cust_name = TD(row.auth_user.display_name, BR(),
1787
_class='os-customer_name')
1789
cust_name.append(get_fullws_label())
1791
product = row.workshops_products_customers.workshops_products_id
1792
cust_name.append(get_label('default', product))
1794
# get attendance checkbox
1795
cuID = row.auth_user.id
1796
attendance = activity_list_customers_get_list_get_attendance(cuID,
1798
table.append(TR(TD(row.auth_user.thumbsmall,
1799
_class='os-customer_image_td'),
1804
table = TABLE(TR(TH(''), TH(''), TH(T('Attending')), _class='header'),
1807
# Add full workshop customers
1808
rows = activity_list_customers_get_fullws_rows(wsID)
1809
table = process_rows(table, rows, fullWS=True)
1811
# Add activity customers
1812
rows = activity_list_customers_get_activity_rows(wsaID)
1813
table = process_rows(table, rows)
1818
def activity_list_customers_get_fullws_rows(wsID):
1820
Returns rows object for full workshops customers of a workshop
1822
orderby = ~db.auth_user.display_name
1823
fwsID = workshops_get_full_workshop_product_id(wsID)
1824
query = (db.workshops_products_customers.Cancelled == False) & \
1825
(db.workshops_products_customers.Waitinglist == False) & \
1826
(db.workshops_products_customers.workshops_products_id == fwsID)
1827
rows = db(query).select(
1829
db.auth_user.archived,
1830
db.auth_user.thumbsmall,
1831
db.auth_user.birthday,
1832
db.auth_user.display_name,
1833
db.workshops_products_customers.workshops_products_id,
1834
left=[db.auth_user.on(db.workshops_products_customers.auth_customer_id == \
1841
def activity_list_customers_get_activity_rows(wsaID):
1843
Returns rows for a workshop activity
1845
orderby = ~db.auth_user.display_name
1846
left = [db.auth_user.on(db.auth_user.id == \
1847
db.workshops_products_customers.auth_customer_id),
1848
db.workshops_products.on(db.workshops_products.id == \
1849
db.workshops_products_customers.workshops_products_id)]
1851
query = activity_list_customers_get_list_activity_query(wsaID)
1852
rows = db(query).select(
1854
db.auth_user.archived,
1855
db.auth_user.thumbsmall,
1856
db.auth_user.birthday,
1857
db.auth_user.display_name,
1858
db.workshops_products_activities.workshops_activities_id,
1859
db.workshops_products_customers.workshops_products_id,
1866
def activity_list_customers_get_list_activity_query(wsaID):
1868
Returns a query that returns a set of all customers in a specific
1869
workshop activity, without the full workshop customers
1871
query = (db.workshops_activities.id ==
1872
db.workshops_products_activities.workshops_activities_id) & \
1873
(db.workshops_products_customers.workshops_products_id ==
1874
db.workshops_products_activities.workshops_products_id) & \
1875
(db.workshops_products_activities.workshops_activities_id ==
1877
(db.workshops_products_customers.Waitinglist == False) & \
1878
(db.workshops_products_customers.Cancelled == False)
1883
def activity_list_customers_get_list_get_attendance(cuID, wsaID):
1885
Checks whether a customer is attending a class
1887
check = db.workshops_activities_customers(auth_customer_id=cuID,
1888
workshops_activities_id=wsaID)
1893
checkbox = INPUT(_name=name,
1898
checkbox = INPUT(_name=name,
1902
hidden_field_cuID = INPUT(_type="hidden",
1905
hidden_field_wsaID = INPUT(_type="hidden",
1909
form = FORM(checkbox, hidden_field_cuID, hidden_field_wsaID)
1914
@auth.requires(auth.has_membership(group_id='Admins') or \
1915
auth.has_permission('read', 'workshops_products_customers'))
1916
def products_list_customers():
1918
Lists customers for a product
1921
if 'wsID' in request.vars:
1922
wsID = request.vars['wsID']
1923
session.workshops_products_list_customers_wsID = wsID
1924
elif session.workshops_products_list_customers_wsID:
1925
wsID = session.workshops_products_list_customers_wsID
1927
# set workshop product ID
1928
if 'wspID' in request.vars:
1929
wspID = request.vars['wspID']
1930
session.workshops_products_list_customers_wspID = wspID
1931
elif session.workshops_products_list_customers_wspID:
1932
wspID = session.workshops_products_list_customers_wspID
1934
session.invoices_edit_back = 'workshops_products_list_customers'
1935
session.invoices_payment_add_back = 'workshops_products_list_customers'
1936
session.workshops_product_resend_info_mail = 'workshops_products_list_customers'
1938
wsp = WorkshopProduct(wspID)
1940
response.title = T('Event')
1941
subtitle = SPAN(get_subtitle(wsID), ' > ', T('Customers'), ': ', wsp.name)
1942
response.subtitle = subtitle
1943
response.view = 'workshops/product_list_customers.html'
1945
session.customers_payment_back = 'workshops'
1946
session.workshops_product_sell_back = None
1949
perm = auth.has_membership(group_id='Admins') or \
1950
auth.has_permission('update', 'workshops_products_customers')
1952
sold_out = product_check_sold_out(wsID, wspID)
1954
search = get_input_search(_id='product_sell_input_search')
1955
modal_content = DIV(
1957
LOAD('workshops', 'product_sell.load',
1958
vars={'wsID': wsID, 'wspID': wspID},
1959
content=os_gui.get_ajax_loader()))
1961
modal_title = SPAN(T("Add customers to"), ' ', wsp.name)
1963
button_text = os_gui.get_modal_button_icon('add', T("Add"))
1964
result = os_gui.get_modal(
1965
button_text=button_text,
1966
button_class='btn-sm',
1967
modal_title=modal_title,
1968
modal_content=modal_content,
1970
modal_class='product_sell_modal',
1971
close_id='product_sell_close'
1973
add = SPAN(result['button'], result['modal'])
1978
T("one or more activities listed in this "),
1979
T("product is fully booked."),
1982
full_wspID = workshops_get_full_workshop_product_id(wsID)
1983
table = TABLE(_class='table')
1985
# add full workshop customers for other activities
1986
# if not int(wspID) == full_wspID:
1987
# table = products_list_customers_get_list(table,
1992
# add customers for selected product
1993
if int(wspID) == full_wspID: # show "Full workshop for Full workhshop act."
1997
table = products_list_customers_get_list(table,
2003
content = DIV(H4(T('Customers for'), ' ', wsp.name), BR(), table)
2005
menu = get_workshops_menu('products', wsID)
2006
back = os_gui.get_button('back', URL('workshops', 'products', vars={'wsID':wsID}))
2007
loader = os_gui.get_ajax_loader(message=T("Refreshing list..."))
2009
return dict(content=content,
2016
def products_list_customers_get_list(table,
2022
Append customers to table
2024
wh = WorkshopsHelper()
2025
db_icwspc = db.invoices_workshops_products_customers
2027
query = (db.workshops_products_customers.workshops_products_id == wspID)
2028
rows = db(query).select(
2029
db.workshops_products_customers.ALL,
2031
db.auth_user.archived,
2032
db.auth_user.thumbsmall,
2033
db.auth_user.birthday,
2034
db.auth_user.display_name,
2036
db.invoices.InvoiceID,
2038
db.invoices.payment_methods_id,
2039
left=[db.auth_user.on(db.workshops_products_customers.auth_customer_id == \
2041
# db.invoices.on(db.invoices.workshops_products_customers_id ==
2042
# db.workshops_products_customers.id)
2043
db.invoices_workshops_products_customers.on(
2044
db_icwspc.workshops_products_customers_id == db.workshops_products_customers.id),
2045
db.invoices.on(db_icwspc.invoices_id == db.invoices.id)],
2046
orderby=db.workshops_products_customers.Cancelled | \
2047
db.workshops_products_customers.Waitinglist | \
2048
db.auth_user.display_name)
2050
left = [db.workshops_products.on(
2051
db.workshops_products.id == \
2052
db.workshops_products_customers.workshops_products_id),
2053
db.workshops.on(db.workshops_products.workshops_id == \
2055
db.invoices_workshops_products_customers.on(
2056
db_icwspc.workshops_products_customers_id ==
2057
db.workshops_products_customers.id),
2058
db.invoices.on(db_icwspc.invoices_id == db.invoices.id)
2061
ih = InvoicesHelper()
2062
for i, row in enumerate(rows):
2063
repr_row = list(rows[i:i + 1].render())[0]
2065
wsp_cuID = row.workshops_products_customers.id
2068
link_text = T('Send')
2069
if row.workshops_products_customers.WorkshopInfo:
2070
link_text = T('Resend')
2071
resend_link = A(link_text, ' ', T('info mail'),
2072
_href=URL('product_resend_info_mail', vars={'wspcID':wsp_cuID}))
2073
event_info = wh.get_customer_info(wsp_cuID,
2075
row.workshops_products_customers.WorkshopInfo,
2078
cust_name = TD(SPAN(row.auth_user.display_name, _class='bold'), BR())
2080
cust_name.append(get_fullws_label())
2082
product = repr_row.workshops_products_customers.workshops_products_id
2083
cust_name.append(get_label('default', product))
2086
if row.workshops_products_customers.Cancelled:
2088
cust_name.append(' ')
2089
cust_name.append(get_cancelled_label())
2091
if row.workshops_products_customers.Waitinglist:
2092
wai_url = URL('product_remove_customer_from_waitinglist',
2093
vars={'wsp_cuID': wsp_cuID,
2096
cust_name.append(' ')
2098
cust_name.append(BR())
2099
cust_name.append(SPAN(T("Waitinglist"),
2100
_class='label label-danger'))
2101
cust_name.append(BR())
2102
if not product_check_sold_out(wsID, wspID):
2103
msg = T("Remove from waitinglist, add to list")
2104
cust_name.append(SPAN(A(msg,
2107
_class='small_font'))
2111
invoice = ih.represent_invoice_for_list(
2113
repr_row.invoices.InvoiceID,
2114
repr_row.invoices.Status,
2115
row.invoices.Status,
2116
row.invoices.payment_methods_id
2121
buttons = products_list_customers_get_buttons(wsID, wsp_cuID)
2123
table.append(TR(TD(repr_row.auth_user.thumbsmall,
2124
_class='os-customer_image_td'),
2128
TD(' ', buttons, _class='td-icons'),
2134
def products_list_customers_get_cancelled(wsp_cuID):
2136
Returns whether or not a customer has cancelled a workshop product
2138
row = db.workshops_products_customers(id=wsp_cuID)
2139
return row.Cancelled
2142
def products_list_customers_get_buttons(wsID, wsp_cuID):
2144
returns buttons for produtcs_list_customers
2146
confirm_remove_msg = T("Really remove this customer from the list?")
2149
cancelled = products_list_customers_get_cancelled(wsp_cuID)
2151
title_cancel = T('Undo cancellation')
2153
title_cancel = T('Cancel customer')
2156
if auth.has_membership(group_id='Admins') or \
2157
auth.has_permission('delete', 'workshops_products_customers'):
2158
btn_delete = os_gui.get_button(
2160
URL('product_delete_customer', vars={'wsID': wsID,
2161
'wsp_cuID': wsp_cuID}),
2163
tooltip=T('Remove customer from list'),
2164
onclick="return confirm('" + confirm_remove_msg + "');",
2167
btn_cancel = os_gui.get_button(
2169
URL('product_cancel_customer',
2171
'wsp_cuID': wsp_cuID}),
2173
tooltip=title_cancel)
2175
return DIV(btn_cancel,
2177
_class='btn-group pull-right')
2180
@auth.requires(auth.has_membership(group_id='Admins') or \
2181
auth.has_permission('update', 'workshops_activities'))
2182
def product_customer_update_info():
2185
This function is meant to be called with the json extension.
2186
It takes id and WorkshopInfo as variables.
2188
if not request.extension == 'json':
2189
return T("Error, please call as JSON")
2191
response.view = 'generic.json'
2195
wsp_cuID = request.vars['id']
2196
row = db.workshops_products_customers(wsp_cuID)
2199
if 'WorkshopInfo' in request.vars:
2200
row.WorkshopInfo = 'T'
2202
row.WorkshopInfo = 'F'
2204
result = row.update_record()
2208
message = T("Saved")
2210
message = T("Uh oh... something went wrong...")
2212
message = T("Error: no data received")
2214
return dict(status=status, message=message)
2217
def products_count_sold(wspID):
2219
Returns count of customers who bought a product
2221
query = (db.workshops_products_customers.workshops_products_id == wspID)
2222
return db(query).count()
2225
def products_get_return_url(wsID):
2227
return the return URL for products add and edit pages
2229
return URL('products', vars={'wsID': wsID})
2232
@auth.requires_login()
2237
wsID = request.args[0]
2239
response.title = T('Event')
2240
response.subtitle = get_subtitle(wsID)
2241
response.view = 'general/tabs_menu.html'
2243
return_url = products_get_return_url(wsID)
2244
next_url = '/workshops/product_activities?wspID=[id]&wsID=' + wsID
2246
db.workshops_products.workshops_id.default = wsID
2247
crud.messages.submit_button = T("Save")
2248
crud.messages.record_created = T("Saved product")
2249
crud.settings.create_next = next_url
2250
crud.settings.create_onaccept = [cache_clear_workshops]
2251
form = crud.create(db.workshops_products)
2253
form_id = "MainForm"
2254
form_element = form.element('form')
2255
form['_id'] = form_id
2257
elements = form.elements('input, select, textarea')
2258
for element in elements:
2259
element['_form'] = form_id
2261
submit = form.element('input[type=submit]')
2263
back = os_gui.get_button('back', return_url)
2264
content = DIV(H4(T('New product')), BR(), form)
2266
menu = get_workshops_menu('products', wsID)
2269
return dict(content=content, back=back, menu=menu, save=submit)
2272
@auth.requires_login()
2277
wspID = request.args[0]
2278
wsID = db.workshops_products(wspID).workshops_id
2280
response.title = T('Event')
2281
response.subtitle = get_subtitle(wsID)
2282
response.view = 'general/tabs_menu.html'
2284
return_url = URL('products',
2285
vars={'wsID': wsID})
2287
crud.messages.submit_button = T("Save")
2288
crud.messages.record_updated = T("Saved product")
2289
crud.settings.update_next = return_url
2290
crud.settings.update_onaccept = [cache_clear_workshops]
2291
crud.settings.update_deletable = False
2292
form = crud.update(db.workshops_products, wspID)
2294
form_id = "MainForm"
2295
form_element = form.element('form')
2296
form['_id'] = form_id
2298
elements = form.elements('input, select, textarea')
2299
for element in elements:
2300
element['_form'] = form_id
2302
submit = form.element('input[type=submit]')
2304
content = DIV(H4(T('Edit product')), BR(), form)
2306
menu = get_workshops_menu('products', wsID)
2308
back = os_gui.get_button('back', URL('products', vars={'wsID':wsID}))
2310
return dict(content=content, back=back, menu=menu, save=submit)
2313
@auth.requires(auth.has_membership(group_id='Admins') or \
2314
auth.has_permission('update', 'workshops_products'))
2315
def product_delete():
2317
Delete a selected product
2319
wsID = request.vars['wsID']
2320
wspID = request.vars['wspID']
2322
if db.workshops_products(id=wspID).Deletable:
2323
query = (db.workshops_products.id == wspID)
2327
cache_clear_workshops()
2329
session.flash = T("Can't delete this product")
2331
redirect(URL('products', vars={'wsID': wsID}))
2334
@auth.requires(auth.has_membership(group_id='Admins') or \
2335
auth.has_permission('update', 'workshops_products'))
2336
def product_activities():
2338
Add or remove activities from a workshop
2340
wsID = request.vars['wsID']
2341
wspID = request.vars['wspID']
2343
product = db.workshops_products(wspID)
2345
response.title = T('Event')
2346
response.subtitle = get_subtitle(wsID)
2347
response.view = 'general/tabs_menu.html'
2349
pquery = (db.workshops_products_activities.workshops_products_id == wspID)
2350
field = db.workshops_products_activities.workshops_activities_id
2351
selected = db(pquery).select(field)
2354
for row in selected:
2355
selected_ids.append(row.workshops_activities_id)
2357
query = (db.workshops_activities.workshops_id == wsID)
2358
rows = db(query).select(db.workshops_activities.ALL)
2360
form = FORM(_id="MainForm")
2361
table = TABLE(THEAD(TR(TH(),
2365
TH(T('Location')))),
2366
_class='table table-hover')
2368
for row in rows.render():
2369
if row.id in selected_ids:
2370
checkbox = INPUT(_name=row.id,
2375
checkbox = INPUT(_name=row.id,
2379
internal_location = not row.school_locations_id is None and \
2380
not row.school_locations_id == ''
2381
if internal_location:
2382
location = row.school_locations_id
2384
location = row.LocationExternal
2386
table.append(TR(TD(checkbox),
2387
TD(row.Activitydate),
2388
TD(row.Starttime + ' - ' + row.Endtime),
2395
content = DIV(DIV(H4(T('Activities included in'), ' ', product.Name), form, _class='col-md-8 clear'),
2398
if form.process().accepted:
2400
for activity_id in form.vars:
2401
if form.vars[activity_id] == 'on':
2402
db.workshops_products_activities.insert(
2403
workshops_activities_id=activity_id,
2404
workshops_products_id=wspID)
2405
session.flash = T('Saved')
2406
redirect(URL('products', vars={'wsID': wsID}))
2408
menu = get_workshops_menu('products', wsID)
2410
back = os_gui.get_button('back', URL('products', vars={'wsID':wsID}))
2412
return dict(content=content,
2414
save=os_gui.get_submit_button('MainForm'),
2418
@auth.requires(auth.has_membership(group_id='Admins') or \
2419
auth.has_permission('update', 'workshops_products'))
2422
Page to select which customer to sell a product to
2424
wsID = request.vars['wsID']
2425
wspID = request.vars['wspID']
2427
wsp = WorkshopProduct(wspID)
2429
response.title = T('Manage')
2430
subtitle = SPAN(get_subtitle(wsID), ' > ',
2431
T('Customers'), ': ', wsp.name, ' > ',
2433
response.subtitle = subtitle
2435
# reset session variable for search
2436
session.customers_load_list_search_name = None
2438
_class = 'clear' + ' '
2439
_class += 'load_list_customers' + ' '
2441
back = os_gui.get_button('back_bs',
2442
URL('products_list_customers', vars={'wsID': wsID,
2447
search_results = DIV(
2448
LOAD('customers', 'load_list.load',
2449
content=os_gui.get_ajax_loader(message=T("Loading...")),
2450
target='product_sell_customers_list',
2451
vars={'list_type': 'workshops_product_sell',
2452
'items_per_page': 10,
2457
_id='product_sell_customers_list')
2459
loader = os_gui.get_ajax_loader(message=T("Searching..."))
2461
return dict(content=search_results, loader=loader)
2464
@auth.requires(auth.has_membership(group_id='Admins') or \
2465
auth.has_permission('update', 'workshops_products_customers'))
2466
def product_sell_to_customer():
2468
Sell a product to a customer
2470
cuID = request.vars['cuID']
2471
wsID = request.vars['wsID']
2472
wspID = request.vars['wspID']
2475
if 'waiting' in request.vars:
2479
session.flash = T("Saved")
2481
wsp = WorkshopProduct(wspID)
2482
wsp.sell_to_customer(cuID, waitinglist=waitinglist)
2484
next_url = product_sell_get_return_url(cuID, wsID, wspID)
2485
if session.workshops_product_sell_back == 'customers':
2486
redirect(next_url, client_side=True)
2491
def product_sell_get_return_url(cuID, wsID, wspID, remove=False):
2493
Return URL for adding a customer to a product or removing a customer
2496
if session.workshops_product_sell_back == 'customers':
2497
url = URL('customers', 'workshops', vars={'cuID': cuID})
2500
url = URL('products_list_customers', vars={'wsID': wsID,
2503
url = URL('product_sell', vars={'wsID': wsID,
2509
# TODO: move to os_workshops.WorkshopProduct so it can also be used in the shop or os_workshops.WorkshopHelper
2510
def product_check_sold_out(wsID, wspID):
2512
This function checks if a product is sold out
2513
It's sold out when any of the activities it contains is completely full
2517
fwsID = workshops_get_full_workshop_product_id(wsID)
2518
if int(wspID) == fwsID:
2519
# Full workshops check, check if any activity is full
2520
query = (db.workshops_activities.workshops_id == wsID)
2521
rows = db(query).select(db.workshops_activities.ALL)
2523
reserved = activity_count_reservations(row.id, fwsID)
2524
if reserved >= row.Spaces:
2528
# Product check, check if any a activity is full
2529
query = (db.workshops_products_activities.workshops_products_id ==
2531
rows = db(query).select(db.workshops_products_activities.ALL)
2533
activity = db.workshops_activities(row.workshops_activities_id)
2534
reserved = activity_count_reservations(activity.id, fwsID)
2535
if reserved >= activity.Spaces:
2542
@auth.requires(auth.has_membership(group_id='Admins') or \
2543
auth.has_permission('update', 'workshops_products_customers'))
2544
def product_delete_customer():
2546
Remove a customer from the sold products list
2548
wsID = request.vars['wsID']
2549
wsp_cuID = request.vars['wsp_cuID']
2551
# Cancel invoice (if any)
2552
row = db.invoices_workshops_products_customers(workshops_products_customers_id = wsp_cuID)
2553
iID = row.invoices_id
2555
invoice = Invoice(iID)
2556
invoice.set_status('cancelled')
2558
# get database record for workshop_customers
2559
row = db.workshops_products_customers(
2560
db.workshops_products_customers.id == wsp_cuID)
2562
cuID = row.auth_customer_id
2563
wspID = row.workshops_products_id
2565
# remove sold product entry for customer
2566
query = (db.workshops_products_customers.id == wsp_cuID)
2569
# remove any payments connected to workshop activity
2570
query = (db.customers_payments.auth_customer_id == cuID) & \
2571
(db.customers_payments.workshops_products_id == wspID)
2574
session.flash = T("Removed")
2576
next_url = product_sell_get_return_url(cuID, wsID, wspID, True)
2580
@auth.requires(auth.has_membership(group_id='Admins') or \
2581
auth.has_permission('update', 'workshops_products_customers'))
2582
def product_cancel_customer():
2584
Change status to cancelled or from cancelled to not cancelled
2586
wsID = request.vars['wsID']
2587
wsp_cuID = request.vars['wsp_cuID']
2589
# Cancel invoice (if any)
2590
row = db.invoices_workshops_products_customers(workshops_products_customers_id = wsp_cuID)
2591
iID = row.invoices_id
2593
invoice = Invoice(iID)
2594
invoice.set_status('cancelled')
2596
# get database record for workshop_customers
2597
row = db.workshops_products_customers(
2598
db.workshops_products_customers.id == wsp_cuID)
2600
cuID = row.auth_customer_id
2601
wspID = row.workshops_products_id
2603
row.Cancelled = not row.Cancelled
2606
# update invoice status to cancelled if status == sent
2607
query = (db.invoices_workshops_products_customers.workshops_products_customers_id == wsp_cuID)
2608
rows = db(query).select(db.invoices_workshops_products_customers.ALL)
2611
iID = row.invoices_id
2613
invoice = Invoice(iID)
2614
if invoice.invoice.Status == 'sent':
2615
invoice.set_status('cancelled')
2617
session.flash = T("Saved")
2619
next_url = product_sell_get_return_url(cuID, wsID, wspID, True)
2623
@auth.requires(auth.has_membership(group_id='Admins') or \
2624
auth.has_permission('update', 'workshops_products_customers'))
2625
def product_remove_customer_from_waitinglist():
2627
Removes a customer from the waitinglist and add customer to list
2629
wsp_cuID = request.vars['wsp_cuID']
2630
wsID = request.vars['wsID']
2632
row = db.workshops_products_customers(wsp_cuID)
2633
wspID = row.workshops_products_id
2634
cuID = row.auth_customer_id
2636
query = (db.workshops_products_customers.id == wsp_cuID)
2639
wsp = WorkshopProduct(wspID)
2640
wsp.sell_to_customer(cuID)
2642
redirect(URL('products_list_customers', vars={'wsID': wsID,
2645
@auth.requires(auth.has_membership(group_id='Admins') or \
2646
auth.has_permission('update', 'workshops_products_customers'))
2647
def product_resend_info_mail():
2649
Resend info mail for customer
2651
wspcID = request.vars['wspcID']
2652
wspc = db.workshops_products_customers(wspcID)
2653
cuID = wspc.auth_customer_id
2654
customer = Customer(cuID)
2659
msgID = osmail.render_email_template('workshops_info_mail', workshops_products_customers_id=wspcID)
2660
sent = osmail.send(msgID, cuID)
2663
# Check the "Event info" checkbox
2666
wspc.WorkshopInfo = True
2667
wspc.update_record()
2668
msg = T('Sent event info mail to ')
2670
msg = T('Unable to send event info mail to ')
2675
session.flash = msg + customer.row.display_name
2677
wsp = db.workshops_products(wspc.workshops_products_id)
2680
if session.workshops_product_resend_info_mail == 'customers_workshops':
2681
redirect(URL('customers', 'workshops', vars={'cuID':cuID}))
2683
redirect(URL('workshops', 'products_list_customers', vars={'wsID':wsp.workshops_id,
2687
def get_fullws_label(value=None):
2689
Returns label for full Workshop
2691
return get_label('primary', T("Full event"))
2694
def get_cancelled_label(value=None):
2696
Returns label for cancelled product
2698
return SPAN(T("Cancelled"), _class='label label-warning')
2701
@auth.requires(auth.has_membership(group_id='Admins') or \
2702
auth.has_permission('update', 'class_status'))
2703
def overlapping_classes():
2705
Shows a page of overlapping classes for a given workshop
2707
wsID = request.vars['wsID']
2708
workshop = db.workshops(wsID)
2709
response.title = T('Overlapping classes')
2711
response.subtitle = workshop.Name + ' ' + \
2712
workshop.Startdate.strftime(DATE_FORMAT)
2713
except AttributeError:
2714
response.subtitle = workshop.Name
2716
session.classes_status_set_cancelled_back = 'workshops_oc'
2718
return_url = URL('activities', vars={'wsID': wsID})
2721
query = (db.workshops_activities.workshops_id == wsID)
2722
rows = db(query).select(db.workshops_activities.ALL,
2723
orderby=db.workshops_activities.Activitydate)
2725
content.append(activity_get_overlapping_classes(row.id))
2727
cancel_oc = A(T('Cancel all overlapping classes'),
2728
_href=URL('overlapping_classes_cancel_all',
2729
vars={'wsID': wsID}),
2730
_class='btn btn-default btn-sm right')
2731
back = os_gui.get_button('back', return_url)
2732
back = DIV(back, cancel_oc, _class='pull-right')
2734
return dict(content=content,
2738
def activity_get_overlapping_classes(wsaID):
2740
Check for overlapping classes
2742
activity = db.workshops_activities(wsaID)
2744
loc_check = not activity.school_locations_id is None or \
2745
not activity.school_locations_id == ''
2747
classes = TABLE(_class='table table-hover')
2748
classes.append(THEAD(TR(TH(),
2750
TH(T('Class type')),
2751
TH(T('Class date')),
2756
_class='os-table_header')))
2757
activity_weekday = get_weekday(activity.Activitydate)
2759
query = overlapping_classes_get_query(activity_weekday,
2760
activity.school_locations_id,
2761
activity.Activitydate,
2765
rows = db(query).select(db.classes.ALL)
2766
for row in rows.render():
2768
wsID = activity.workshops_id
2769
result = classes_get_status(row.id, activity.Activitydate)
2770
status = result['status_marker']
2771
buttons = activity_get_overlapping_classes_buttons(
2774
activity.Activitydate,
2777
location = max_string_length(row.school_locations_id, 15)
2778
classtype = max_string_length(row.school_classtypes_id, 24)
2779
start = row.Starttime
2781
teachers = class_get_teachers(clsID,
2782
activity.Activitydate)
2784
teacher = teachers['teacher']
2786
tr = TR(TD(status, _class='td_status_marker'),
2787
TD(location, _class='location'),
2788
TD(classtype, _class='classtype'),
2789
TD(activity.Activitydate, _class='class_date'),
2790
TD(start, _class='class_time'),
2791
TD(end, _class='class_time'),
2792
TD(teacher, _class='class_teacher'),
2793
TD(buttons, _class='show_buttons'))
2796
if len(classes) == 1: # only header, so no content
2800
classes = T("No overlapping classes found.")
2802
return dict(activity=activity,
2806
def overlapping_classes_set_status_cancelled_execute(clsID, date):
2808
Actually cancels the class
2810
cotc = db.classes_otc(classes_id=clsID, ClassDate=date)
2812
db.classes_otc.insert(
2818
cotc.Status = 'cancelled'
2819
cotc.update_record()
2822
def overlapping_classes_set_status_cancelled():
2824
Cancels an overlapping class
2826
wsID = request.vars['wsID']
2827
clsID = request.vars['clsID']
2828
date_formatted = request.vars['date']
2829
date = datestr_to_python(DATE_FORMAT, date_formatted)
2831
overlapping_classes_set_status_cancelled_execute(clsID, date)
2833
redirect(URL('overlapping_classes', vars={'wsID': wsID}))
2836
def overlapping_classes_set_status_normal():
2838
Sets status to normal
2840
wsID = request.vars['wsID']
2841
clsID = request.vars['clsID']
2842
date_formatted = request.vars['date']
2843
date = datestr_to_python(DATE_FORMAT, date_formatted)
2845
cotc = db.classes_otc(classes_id=clsID, ClassDate=date)
2846
if cotc: # No need to do anything when there are no changes
2847
cotc.Status = 'normal'
2848
cotc.update_record()
2851
if the status is normal:
2852
check if there are any other changes
2853
otherwise just delete. '''
2856
cotc.school_locations_id,
2857
cotc.school_classtypes_id,
2860
cotc.auth_teacher_id,
2861
cotc.auth_teacher_id2,
2864
for field in fields:
2865
if not field is None or field == '':
2869
query = (db.classes_otc.id == cotc.id)
2870
result = db(query).delete()
2872
redirect(URL('overlapping_classes', vars={'wsID': wsID}))
2875
def overlapping_classes_cancel_all():
2877
Function called which sets all overlapping classes to cancelled
2879
wsID = request.vars['wsID']
2880
# Get all activities for a workshop
2881
query = (db.workshops_activities.workshops_id == wsID)
2882
rows = db(query).select(db.workshops_activities.ALL)
2884
# Get overlapping classes for activity
2885
date = row.Activitydate
2886
weekday = get_weekday(date)
2887
oquery = overlapping_classes_get_query(weekday,
2888
row.school_locations_id,
2892
crows = db(oquery).select(db.classes.ALL)
2895
overlapping_classes_set_status_cancelled_execute(clsID, date)
2897
redirect(URL('overlapping_classes', vars={'wsID': wsID}))
2900
def activity_get_overlapping_classes_buttons(wsID, clsID, class_date, status):
2902
Returns (un)cancel buttons for a class
2904
btn_group = DIV(_class='btn-group pull-right')
2905
class_date = class_date.strftime(DATE_FORMAT)
2906
if status == 'cancelled':
2907
btn = A(SPAN(_class='glyphicon glyphicon-ok'),
2908
_href=URL('overlapping_classes_set_status_normal',
2909
vars={'clsID': clsID,
2912
_title=T('Set status Normal'),
2913
_class='btn btn-default btn-sm green')
2915
btn = A(SPAN(_class='glyphicon glyphicon-ban-circle'),
2916
_href=URL('overlapping_classes_set_status_cancelled',
2917
vars={'clsID': clsID,
2920
_title=T('Set status Cancelled'),
2921
_class='btn btn-default btn-sm red')
2923
btn_group.append(btn)
2928
def overlapping_classes_get_query(activity_weekday,
2929
activity_locations_id,
2934
Returns query to check for overlapping classes
2936
return (db.classes.school_locations_id == activity_locations_id) & \
2937
(db.classes.Startdate <= activity_date) & \
2938
((db.classes.Enddate >= activity_date) |
2939
(db.classes.Enddate == None)) & \
2940
(db.classes.Week_day == activity_weekday) & \
2941
(((db.classes.Starttime <= starttime) &
2942
(db.classes.Endtime <= endtime) &
2943
(db.classes.Endtime >= endtime)) |
2944
((db.classes.Starttime >= starttime) &
2945
(db.classes.Starttime <= endtime) &
2946
(db.classes.Endtime <= endtime) &
2947
(db.classes.Endtime >= starttime)) |
2948
((db.classes.Starttime >= starttime) &
2949
(db.classes.Starttime <= endtime) &
2950
(db.classes.Endtime >= endtime)) |
2951
((db.classes.Starttime <= starttime) &
2952
(db.classes.Endtime >= endtime))
2956
def overlapping_classes_get_count(wsaID):
2958
Returns count for overlapping classes
2960
activity = db.workshops_activities(wsaID)
2961
activity_weekday = get_weekday(activity.Activitydate)
2962
query = overlapping_classes_get_query(activity_weekday,
2963
activity.school_locations_id,
2964
activity.Activitydate,
2967
return db(query).count()
2970
def overlapping_classes_get_count_all(wsID):
2972
Returns count of all overlapping classes for a workshop
2975
query = (db.workshops_activities.workshops_id == wsID)
2976
rows = db(query).select(db.workshops_activities.ALL)
2978
count += overlapping_classes_get_count(row.id)
2983
# @auth.requires(auth.has_membership(group_id='Admins') or \
2984
# auth.has_permission('update', 'workshops_mail_customers'))
2985
# def mail_customers():
2987
# Send an email to all customers for a workshop
2988
# Checks db.workshops_products_customers to see who should get the mail
2991
# response.js = 'tinymce_init_default();'
2993
# wsID = request.vars['wsID']
2995
# form = mail_customers_get_form()
2999
# if form.process().accepted:
3000
# mail_customers_send(wsID,
3001
# form.vars.subject,
3002
# '<html><body>' + XML(form.vars.message) + '</body></html>')
3004
# response.flash = T("Message sent")
3005
# redirect(URL('messages', vars={'wsID': wsID}, extension=False),
3009
# msg = SPAN(B(T('Oh snap!')), ' ',
3010
# T("Change a few things up and try sending again..."))
3011
# form_msg = os_gui.get_alert('danger', msg)
3013
# response.flash = None
3016
# form.custom.begin,
3017
# form.custom.widget.subject,
3018
# form.custom.widget.message,
3019
# form.custom.submit,
3023
# return dict(content=content)
3026
# def mail_customers_send(wsID, subject, message, msgID=None):
3028
# send mail to customers of a workshop
3030
# # Save message to database
3032
# msgID = db.messages.insert(msg_subject=subject,
3033
# msg_content=message)
3034
# db.workshops_messages.insert(workshops_id=wsID,
3035
# messages_id=msgID)
3037
# # To send, first get list of all customers with email for a workshop
3038
# wh = WorkshopsHelper()
3039
# customers_rows = wh.get_all_workshop_customers(wsID)
3042
# for row in customers_rows:
3043
# osmail.send(msgID, row.auth_user.id)
3045
# # Get customers for those products
3048
# def mail_customers_get_form(value=None):
3050
# Returns a form to mail customers
3052
# signature = '<br><br>'
3053
# if db.sys_properties(Property='smtp_signature'):
3054
# signature += db.sys_properties(Property='smtp_signature').PropertyValue
3056
# form = SQLFORM.factory(
3058
# requires=IS_NOT_EMPTY()),
3059
# Field('message', 'text',
3060
# default=signature,
3061
# requires=IS_NOT_EMPTY()),
3062
# submit_button=T("Send"))
3064
# confirm_send_msg = T("Are you sure you want to send this message?")
3065
# submit_onclick = "return confirm('" + confirm_send_msg + "');"
3067
# form.element('#no_table_subject').attributes['_placeholder'] = T("Subject...")
3068
# form.element('#no_table_message').attributes['_placeholder'] = T("Message...")
3069
# form.element('input[type=submit]').attributes['_onclick'] = submit_onclick
3071
# # add class for BS3
3072
# form.custom.widget.subject['_class'] += ' form-control'
3073
# # form.custom.widget.message['_class'] += ' form-control'
3074
# form.custom.widget.message['_class'] += ' form-control tmced'
3075
# form.custom.submit['_class'] = ' btn-primary'
3080
# @auth.requires(auth.has_membership(group_id='Admins') or \
3081
# auth.has_permission('read', 'workshops_messages'))
3084
# List messages sent to customers for a workshop.
3086
# wsID = request.vars['wsID']
3087
# response.title = T('Event')
3088
# response.subtitle = get_subtitle(wsID)
3089
# session.workshops_msgID = None
3090
# # response.view = 'workshops/manage.html'
3092
# ## Modals container
3095
# ## Mail button & modal begin ##
3096
# result = messages_get_mail(wsID)
3097
# btn_mail = result['button']
3098
# modals.append(result['modal'])
3100
# ## Mail button & modal end ##
3102
# ## Message list begin ##
3103
# content_msg = DIV(_class='container_messages')
3105
# h = html2text.HTML2Text()
3106
# h.ignore_links = True
3107
# h.images_to_alt = True
3109
# messages = UL(_class='ul_liststyle_none list_messages col-md-3')
3110
# left = [db.messages.on(db.workshops_messages.messages_id == db.messages.id)]
3111
# query = (db.workshops_messages.workshops_id == wsID)
3112
# rows = db(query).select(db.messages.ALL,
3113
# db.workshops_messages.ALL,
3115
# orderby=~db.workshops_messages.Created_at)
3116
# for i, row in enumerate(rows):
3117
# li_class = 'os-clickable'
3119
# session.workshops_msgID = row.messages.id
3120
# li_class += ' active'
3122
# created_at = row.workshops_messages.Created_at
3123
# created_at = created_at.strftime(DATE_FORMAT)
3124
# msg_preview = h.handle(row.messages.msg_content)
3125
# msg_preview = msg_preview.replace('*', '')
3126
# msg_preview = msg_preview.replace('_', '')
3127
# msg_preview = SPAN(XML(msg_preview[0:60]),
3128
# _class='vsmall_font grey')
3130
# subject = max_string_length(row.messages.msg_subject, 25)
3132
# messages.append(LI(B(subject), ' ',
3134
# _class='right vsmall_font grey'),
3138
# _id=row.messages.id))
3140
# content_msg.append(messages)
3142
# ## Message list end ##
3144
# ## Message display begin ##
3145
# message_display = LOAD('messages', 'message.load',
3147
# target='message_display',
3148
# content=os_gui.get_ajax_loader(),
3149
# vars={'category': 'workshops',
3152
# content_msg.append(DIV(message_display,
3153
# _class='col-md-9 message_display'))
3155
# ## Message display end ##
3157
# content = DIV(DIV(content_msg, modals, _class='col-md-12'),
3160
# menu = get_workshops_menu(request.function, wsID)
3161
# back = manage_get_back()
3163
# return dict(back=back,
3166
# btn_mail=DIV(btn_mail, _class='pull-right'),
3167
# left_sidebar_enabled=True)
3170
# def messages_get_mail(wsID):
3172
# Returns mail button and modal for all workshop customers
3174
# workshop = db.workshops(wsID)
3175
# modal_title = T("Mail all customers: ") + workshop.Name
3176
# modal_content = LOAD('workshops', 'mail_customers.load',
3178
# vars={'wsID': wsID})
3180
# btn_icon = SPAN(SPAN(_class='glyphicon glyphicon-envelope'), ' ',
3181
# T('Send message'))
3183
# result = os_gui.get_modal(button_text=XML(btn_icon),
3184
# modal_title=modal_title,
3185
# modal_content=modal_content,
3186
# modal_class='mail_' + unicode(wsID),
3188
# button_class='btn-sm')
3193
# @auth.requires(auth.has_membership(group_id='Admins') or \
3194
# auth.has_permission('read', 'workshops_messages'))
3195
# def messages_set_id():
3197
# Set session ID used to show message in main window of messages pane
3198
# This function should be called as JSON
3200
# if request.extension == 'json':
3201
# session.workshops_msgID = request.vars['msgID']
3202
# status = 'success'
3206
# message = T('Please call this function as "json"')
3208
# return dict(status=status,
3212
@auth.requires(auth.has_membership(group_id='Admins') or \
3213
auth.has_permission('read', 'tasks'))
3216
Display list of tasks for a workshop
3218
wsID = request.vars['wsID']
3219
# now continue settings things as usual
3220
workshop = db.workshops(wsID)
3221
response.title = T('Event')
3222
response.subtitle = get_subtitle(wsID)
3223
response.view = 'general/tabs_menu.html'
3225
tasks = DIV(LOAD('tasks', 'list_tasks.load',
3227
content=os_gui.get_ajax_loader()))
3233
permission = auth.has_membership(group_id='Admins') or \
3234
auth.has_permission('create', 'tasks')
3236
# add = os_gui.get_button('add', url_add)
3238
add = th.add_get_modal({'wsID': wsID})
3240
back = manage_get_back()
3241
menu = get_workshops_menu(request.function, wsID)
3243
return dict(content=content,
3249
# No decorator here, permissions are checked inside the function
3252
Converts a invoice to PDF
3256
wsID = request.vars['wsID']
3257
workshop = Workshop(wsID)
3259
permission = (auth.has_membership(group_id='Admins') or
3260
auth.has_permission('read', 'workshops'))
3263
return T("Not authorized")
3265
html = pdf_template(wsID)
3267
fname = workshop.Startdate.strftime(DATE_FORMAT) + u'_' + workshop.Name + u'.pdf'
3268
response.headers['Content-Type'] = 'application/pdf'
3269
response.headers['Content-disposition'] = 'attachment; filename=' + fname
3270
# return pyfpdf_from_html(html)
3272
stream = cStringIO.StringIO()
3273
workshop = weasyprint.HTML(string=html).write_pdf(stream)
3275
return stream.getvalue()
3278
@auth.requires(auth.has_membership(group_id='Admins') or \
3279
auth.has_permission('read', 'workshops'))
3280
def pdf_template_show():
3281
wsID = request.vars['wsID']
3283
return pdf_template(wsID)
3286
def pdf_template(wsID):
3288
Print friendly display of a Workshop
3290
template = get_sys_property('branding_default_template_workshops') or 'default.html'
3291
template_file = 'templates/workshops/' + template
3293
workshop = Workshop(wsID)
3294
activities = workshop.get_activities()
3295
products = workshop.get_products()
3297
if len(activities) == 0:
3298
session.flash = T('No activities found, unble to export to PDF')
3299
redirect(URL('index'))
3300
elif len(products) == 0:
3301
session.fash = T('No products found, unable to export to PDF')
3302
redirect(URL('index'))
3304
price = format(products[0].Price or 0, '.2f')
3306
workshop_image_url = URL('default', 'download', args=workshop.picture, host=True, scheme=True)
3308
html = response.render(template_file,
3309
dict(workshop=workshop,
3310
workshop_image_url=workshop_image_url,
3311
dates=pdf_template_get_display_dates(workshop, activities),
3312
times=pdf_template_get_display_times(workshop, activities),
3313
activities=activities,
3316
logo=pdf_template_get_logo()))
3321
def pdf_template_get_logo(var=None):
3323
Returns logo for pdf template
3325
branding_logo = os.path.join(request.folder,
3327
'plugin_os-branding',
3329
'branding_logo_invoices.png')
3330
if os.path.isfile(branding_logo):
3331
abs_url = '%s://%s/%s/%s' % (request.env.wsgi_url_scheme,
3332
request.env.http_host,
3334
'plugin_os-branding/logos/branding_logo_invoices.png')
3335
logo_img = IMG(_src=abs_url)
3343
def pdf_template_get_display_dates(workshop, activities):
3345
:param workshop: Workshop object
3346
:param activities: workshop activities rows
3347
:return: formatted date for workshop
3350
if len(activities) > 0:
3351
date_from = activities[0].Activitydate
3353
if len(activities) > 1:
3354
date_until = activities[len(activities) - 1].Activitydate
3356
if len(activities) == 0: # no activities
3357
date_from = T("No activities found...")
3358
date_until = T("No activities found...")
3360
return dict(date_from=date_from,
3361
date_until=date_until)
3364
def pdf_template_get_display_times(workshop, activities):
3366
:param workshop: Workshop object
3367
:param activities: workshop activities rows
3368
:return: formatted date for workshop
3371
if len(activities) > 0:
3372
time_from = activities[0].Starttime
3374
if len(activities) > 1:
3375
time_until = activities[len(activities) - 1].Endtime
3377
if len(activities) == 0: # no activities
3378
date_from = T("No activities found...")
3379
date_until = T("No activities found...")
3381
return dict(time_from=time_from,
3382
time_until=time_until)
3385
@auth.requires(auth.has_membership(group_id='Admins') or \
3386
auth.has_permission('update', 'workshops_info_mail'))
3389
Information mail for workshops
3391
wsID = request.vars['wsID']
3392
workshop = db.workshops(wsID)
3393
response.title = T('Event')
3394
response.subtitle = get_subtitle(wsID)
3395
response.view = 'general/tabs_menu.html'
3400
row = db.workshops_mail(workshops_id = wsID)
3403
wsmID = db.workshops_mail.insert(
3404
workshops_id = wsID,
3411
crud.messages.submit_button = T("Save")
3412
crud.messages.record_updated = T("Saved")
3413
crud.settings.formstyle = 'bootstrap3_stacked'
3414
crud.settings.update_next = URL('info_mail', vars={'wsID':wsID})
3415
form = crud.update(db.workshops_mail, wsmID)
3417
result = set_form_id_and_get_submit_button(form, 'MainForm')
3418
form = result['form']
3419
submit = result['submit']
3421
textareas = form.elements('textarea')
3422
for textarea in textareas:
3423
textarea['_class'] += ' tmced'
3427
# preview = os_gui.get_button('notext',
3428
# URL('workshops', 'info_mail_preview', vars={'wsmID':wsmID}),
3429
# title=T('Preview'),
3430
# _class='pull-right',
3434
menu = get_workshops_menu(request.function, wsID)
3435
back = manage_get_back()
3437
return dict(content=content,
b'\\ No newline at end of file'