1
# -*- coding: utf-8 -*-
7
import cPickle as pickle
9
from gluon.admin import *
10
from gluon.fileutils import listdir
14
# Options Menu (available in all Functions' Views)
15
# - can Insert/Delete items from default menus within a function, if required.
16
response.menu_options = admin_menu_options
18
# S3 framework functions
22
module_name = deployment_settings.modules[module].name_nice
24
return dict(module_name=module_name)
26
@auth.shn_requires_membership(1)
28
"RESTful CRUD controller"
32
table.admin_name.label = T("Admin Name")
33
table.admin_email.label = T("Admin Email")
34
table.admin_tel.label = T("Admin Tel")
35
table.utc_offset.label = T("UTC Offset")
36
table.theme.label = T("Theme")
37
table.theme.comment = DIV(A(T("Add Theme"), _class="colorbox", _href=URL(r=request, c="admin", f="theme", args="create", vars=dict(format="popup")), _target="top", _title=T("Add Theme"))),
38
table.debug.label = T("Debug")
39
table.debug.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Debug|Switch this on to use individual CSS/Javascript files for diagnostics during development."))
40
table.self_registration.label = T("Self Registration")
41
table.self_registration.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Self-registration|Can users register themselves for authenticated login access?"))
42
table.security_policy.label = T("Security Policy")
43
table.security_policy.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Security Policy|The simple policy allows anonymous users to Read & registered users to Edit. The full security policy allows the administrator to set permissions on individual tables or records - see models/zzz.py."))
44
table.archive_not_delete.label = T("Archive not Delete")
45
table.archive_not_delete.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Archive not Delete|If this setting is enabled then all deleted records are just flagged as deleted instead of being really deleted. They will appear in the raw database access but won't be visible to normal users."))
46
table.audit_read.label = T("Audit Read")
47
table.audit_read.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Audit Read|If enabled then a log is maintained of all records a user accesses. If disabled then it can still be enabled on a per-module basis."))
48
table.audit_write.label = T("Audit Write")
49
table.audit_write.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Audit Write|If enabled then a log is maintained of all records a user edits. If disabled then it can still be enabled on a per-module basis."))
51
s3.crud_strings.setting.title_update = T("Edit Settings")
52
s3.crud_strings.setting.msg_record_modified = T("Settings updated")
53
s3.crud_strings.setting.label_list_button = None
54
#crud.settings.update_next = URL(r=request, args=[1, "update"])
56
s3xrc.model.configure(table,
57
#onvalidation=theme_check,
59
return shn_rest_controller("s3", "setting", deletable=False, listadd=False)
60
s3xrc.model.clear_config(table, "onvalidation", "onaccept")
62
@auth.shn_requires_membership(1)
64
"RESTful CRUD controller"
66
tablename = module + "_" + resource
70
table.name.label = T("Name")
71
table.name.comment = SPAN("*", _class="req")
72
#table.logo.label = T("Logo")
73
#table.logo.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Logo|Name of the file (& optional sub-path) located in static which should be used for the top-left image."))
74
#table.header_background.label = T("Header Background")
75
#table.header_background.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Header Background|Name of the file (& optional sub-path) located in static which should be used for the background of the header."))
76
#table.footer.label = T("Footer")
77
#table.footer.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Footer|Name of the file (& optional sub-path) located in views which should be used for footer."))
78
table.text_direction.label = T("Text Direction")
79
table.text_direction.comment = A(SPAN("[Help]"), _class="tooltip", _title=T("Text Direction|Whilst most languages are read from Left-to-Right, Arabic, Hebrew & Farsi go from Right-to-Left."))
80
table.col_background.label = T("Background Colour")
81
table.col_txt.label = T("Text Colour for Text blocks")
82
table.col_txt_background.label = T("Background Colour for Text blocks")
83
table.col_txt_border.label = T("Border Colour for Text blocks")
84
table.col_txt_underline.label = T("Colour for Underline of Subheadings")
85
table.col_menu.label = T("Colour of dropdown menus")
86
table.col_highlight.label = T("Colour of selected menu items")
87
table.col_input.label = T("Colour of selected Input fields")
88
table.col_border_btn_out.label = T("Colour of bottom of Buttons when not pressed")
89
table.col_border_btn_in.label = T("Colour of bottom of Buttons when pressed")
90
table.col_btn_hover.label = T("Colour of Buttons when hovering")
93
ADD_THEME = T("Add Theme")
94
LIST_THEMES = T("List Themes")
95
s3.crud_strings[resource] = Storage(
96
title_create = ADD_THEME,
97
title_display = T("Theme Details"),
98
title_list = LIST_THEMES,
99
title_update = T("Edit Theme"),
100
title_search = T("Search Themes"),
101
subtitle_create = T("Add New Theme"),
102
subtitle_list = T("Themes"),
103
label_list_button = LIST_THEMES,
104
label_create_button = ADD_THEME,
105
msg_record_created = T("Theme added"),
106
msg_record_modified = T("Theme updated"),
107
msg_record_deleted = T("Theme deleted"),
108
msg_list_empty = T("No Themes currently defined"))
110
s3xrc.model.configure(table,
111
#onvalidation=theme_check,
112
#list_fields=["id", "name", "logo", "footer", "col_background"],
113
list_fields=["id", "name", "col_background"],
116
return shn_rest_controller(module, resource)
117
s3xrc.model.clear_config(table, "onvalidation")
119
def theme_apply(form):
120
"Apply the Theme specified by Form"
124
template = os.path.join(request.folder, "static", "styles", "S3", "template.css")
125
tmp_folder = os.path.join(request.folder, "static", "scripts", "tools")
126
out_file = os.path.join(request.folder, "static", "styles", "S3", "sahana.css")
127
out_file2 = os.path.join(request.folder, "static", "styles", "S3", "sahana.min.css")
129
if not os.access(template, os.R_OK):
130
session.error = T("Template file %s not readable - unable to apply theme!" % template)
131
redirect(URL(r=request, args=request.args))
132
if not os.access(tmp_folder, os.W_OK):
133
session.error = T("Temp folder %s not writable - unable to apply theme!" % tmp_folder)
134
redirect(URL(r=request, args=request.args))
135
if not os.access(out_file, os.W_OK):
136
session.error = T("CSS file %s not writable - unable to apply theme!" % out_file)
137
redirect(URL(r=request, args=request.args))
138
if not os.access(out_file2, os.W_OK):
139
session.error = T("CSS file %s not writable - unable to apply theme!" % out_file2)
140
redirect(URL(r=request, args=request.args))
142
inpfile = open(template, "r")
143
lines = inpfile.readlines()
145
# Read settings from Database
146
theme = db(db.admin_theme.id == form.vars.theme).select(limitby=(0, 1)).first()
147
default_theme = db(db.admin_theme.id == 1).select(limitby=(0, 1)).first()
151
# logo = default_theme.logo
152
#if theme.header_background:
153
# header_background = theme.header_background
155
# header_background = default_theme.header_background
156
if theme.text_direction:
157
text_direction = theme.text_direction
159
text_direction = "ltr"
161
ofile = open(out_file, "w")
163
#line = line.replace("YOURLOGOHERE", logo)
164
#line = line.replace("HEADERBACKGROUND", header_background )
165
line = line.replace("TEXT_DIRECTION", text_direction)
166
# Iterate through Colours
167
for key in theme.keys():
168
if key[:4] == "col_":
170
line = line.replace(key, theme[key])
172
line = line.replace(key, default_theme[key])
177
from subprocess import PIPE, check_call
178
currentdir = os.getcwd()
179
os.chdir(os.path.join(currentdir, request.folder, "static", "scripts", "tools"))
181
# If started as a Windows service, os.sys.executable is no longer python
182
if ("win" in sys.platform):
183
pythonpath = os.path.join(sys.prefix, "python.exe")
185
pythonpath = os.sys.executable
187
proc = check_call([pythonpath, "build.sahana.py", "CSS", "NOGIS"], stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=False)
189
session.error = T("Error encountered while applying the theme.")
190
redirect(URL(r=request, args=request.args))
193
# Don't do standard redirect to List view as we only want this option available
194
redirect(URL(r=request, args=[1, "update"]))
196
session.error = INVALIDREQUEST
197
redirect(URL(r=request))
199
def theme_check(form):
200
"Check the Theme has valid files available. Deprecated"
201
# Check which form we're called by
203
# Called from Settings
204
theme = db(db.admin_theme.id == form.vars.theme).select(db.admin_theme.logo, limitby=(0, 1)).first()
206
#header_background = theme.header_background
207
#footer = theme.footer
208
#elif form.vars.logo and form.vars.footer:
211
logo = form.vars.logo
212
#header_background = form.vars.header_background
213
#footer = form.vars.footer
215
session.error = INVALIDREQUEST
216
redirect(URL(r=request))
218
_logo = os.path.join(request.folder, "static", logo)
219
#_header_background = os.path.join(request.folder, "static", header_background)
220
#_footer = os.path.join(request.folder, "views", footer)
221
if not os.access(_logo, os.R_OK):
222
form.errors["logo"] = T("Logo file %s missing!" % logo)
224
#if not os.access(_header_background, os.R_OK):
225
# form.errors["header_background"] = T("Header background file %s missing!" % logo)
227
#if not os.access(_footer, os.R_OK):
228
# form.errors["footer"] = T("Footer file %s missing!" % footer)
233
@auth.shn_requires_membership(1)
235
"RESTful CRUD controller"
238
tablename = module + "_" + resource
239
table = db[tablename]
244
ADD_USER = T("Add User")
245
LIST_USERS = T("List Users")
246
s3.crud_strings[tablename] = Storage(
247
title_create = ADD_USER,
248
title_display = T("User Details"),
249
title_list = LIST_USERS,
250
title_update = T("Edit User"),
251
title_search = T("Search Users"),
252
subtitle_create = T("Add New User"),
253
subtitle_list = T("Users"),
254
label_list_button = LIST_USERS,
255
label_create_button = ADD_USER,
256
label_delete_button = T("Delete User"),
257
msg_record_created = T("User added"),
258
msg_record_modified = T("User updated"),
259
msg_record_deleted = T("User deleted"),
260
msg_list_empty = T("No Users currently registered"))
262
# Add users to Person Registry & 'Authenticated' role
263
crud.settings.create_onaccept = lambda form: auth.shn_register(form)
265
# Allow the ability for admin to Disable logins
266
db.auth_user.registration_key.writable = True
267
db.auth_user.registration_key.readable = True
268
db.auth_user.registration_key.label = T("Disabled?")
269
# In Controller to allow registration to work with UUIDs - only manual edits need this setting
270
db.auth_user.registration_key.requires = IS_NULL_OR(IS_IN_SET(["disabled", "pending"]))
274
if jr.method == "update":
275
# Send an email to user if their account is approved
276
# (=moved from 'pending' to 'blank'(i.e. enabled))
277
s3xrc.model.configure(table,
278
onvalidation = lambda form: user_approve(form))
280
response.s3.prep = user_prep
283
def user_postp(jr, output):
284
shn_action_buttons(jr)
286
response.s3.postp = user_postp
288
output = shn_rest_controller(module, resource, main="first_name")
290
s3xrc.model.clear_config(table, "onvalidation", "onaccept")
294
def user_approve(form):
295
"Send an email to user if their account is approved (moved from 'pending' to 'blank'(i.e. enabled))"
296
if form.vars.registration_key:
300
# Now blank - lookup old value
301
status = db(db.auth_user.id == request.vars.id).select(db.auth_user.registration_key, limitby=(0, 1)).first().registration_key
302
if status == "pending":
303
# Send email to user confirming that they are now able to login
304
if not auth.settings.mailer or \
305
not auth.settings.mailer.send(to=form.vars.email,
306
subject=s3.messages.confirmation_email_subject,
307
message=s3.messages.confirmation_email):
308
session.warning = auth.messages.unable_send_email
313
@auth.shn_requires_membership(1)
316
User update form with groups
317
- NB This is currently unused & has no custom view
319
user = request.vars.user
321
# redirect to the user list if user id is not given
323
redirect(URL(r=request, f="user"))
326
# gather common variables
328
data["user_id"] = user
329
data["username"] = db.auth_user[user].first_name + " " + \
330
db.auth_user[user].last_name
331
data["role"] = db.auth_group[user].role
333
# display the standard user details
334
record = db(db.auth_user.id == user).select().first()
335
db.auth_user.id.readable = False
337
# Let admin view and modify the registration key
338
db.auth_user.registration_key.writable = True
339
db.auth_user.registration_key.readable = True
340
db.auth_user.registration_key.label = T("Disabled?")
341
db.auth_user.registration_key.requires = IS_NULL_OR(IS_IN_SET(["disabled", "pending"]))
343
form = SQLFORM(db.auth_user, record, deletable=True)
345
# find all groups user belongs to
346
query = (db.auth_membership.user_id == user)
347
allgroups = db().select(db.auth_group.ALL)
348
user_membership = db(query).select(db.auth_membership.ALL)
350
# db.auth_group[row.group_id].role
351
#records = SQLTABLE(db(query).select(db.auth_membership.ALL))
353
# handle the M to M of user to group membership for display
355
for group in allgroups:
359
for row in user_membership:
361
if (row.group_id == group.id):
362
records.append([group.role, "on", group.id])
363
user_group_count += 1
365
if (user_group_count == 0):
366
# if the group does not exist currently and is enabled
367
#if request.has_key(group.id):
369
db.auth_membership.insert(user_id = user, group_id = group.id)
370
records.append([group.role, "on", group.id])
371
data["heehe"] = "yes %d" % group.id
373
records.append([group.role, "", group.id])
375
# Update records for user details
376
if form.accepts(request.vars): \
377
response.flash="User " + data["username"] + " Updated"
379
response.flash="There were errors in the form"
381
# Update records for group membership details
382
for key in request.vars.keys():
383
data["m_" + key] = request.vars[key]
385
return dict(data=data, records=records, form=form)
387
@auth.shn_requires_membership(1)
389
"RESTful CRUD controller"
392
table = module + "_" + resource
397
ADD_ROLE = T("Add Role")
398
LIST_ROLES = T("List Roles")
399
s3.crud_strings[table] = Storage(
400
title_create = ADD_ROLE,
401
title_display = T("Role Details"),
402
title_list = LIST_ROLES,
403
title_update = T("Edit Role"),
404
title_search = T("Search Roles"),
405
subtitle_create = T("Add New Role"),
406
subtitle_list = T("Roles"),
407
label_list_button = LIST_ROLES,
408
label_create_button = ADD_ROLE,
409
msg_record_created = T("Role added"),
410
msg_record_modified = T("Role updated"),
411
msg_record_deleted = T("Role deleted"),
412
msg_list_empty = T("No Roles currently defined"))
414
return shn_rest_controller(module, resource, main="role")
417
@auth.shn_requires_membership(1)
419
"RESTful CRUD controller"
421
resource = "membership"
422
table = module + "_" + resource
427
table = "auth_membership"
428
ADD_MEMBERSHIP = T("Add Membership")
429
LIST_MEMBERSHIPS = T("List Memberships")
430
s3.crud_strings[table] = Storage(
431
title_create = ADD_MEMBERSHIP,
432
title_display = T("Membership Details"),
433
title_list = LIST_MEMBERSHIPS,
434
title_update = T("Edit Membership"),
435
title_search = T("Search Memberships"),
436
subtitle_create = T("Add New Membership"),
437
subtitle_list = T("Memberships"),
438
label_list_button = LIST_MEMBERSHIPS,
439
label_create_button = ADD_MEMBERSHIP,
440
msg_record_created = T("Membership added"),
441
msg_record_modified = T("Membership updated"),
442
msg_record_deleted = T("Membership deleted"),
443
msg_list_empty = T("No Memberships currently defined"))
445
return shn_rest_controller(module, resource, main="user_id")
447
@auth.shn_requires_membership(1)
449
"List/amend which users are in a Group"
452
group = int(request.args(0))
453
except TypeError, ValueError:
454
session.error = T("Need to specify a role!")
455
redirect(URL(r=request, f="group"))
457
table = db.auth_membership
458
query = table.group_id == group
459
title = str(T("Role")) + ": " + db.auth_group[group].role
460
description = db.auth_group[group].description
461
# Start building the Return
462
output = dict(title=title, description=description, group=group)
464
if auth.settings.username:
465
username = "username"
470
crud.settings.create_onaccept = lambda form: shn_audit_create(form, module, "membership", "html")
471
# Many<>Many selection (Deletable, no Quantity)
473
sqlrows = db(query).select()
483
_user = db.auth_user[id]
484
item_first = _user.first_name
485
item_second = _user.last_name
486
item_description = _user[username]
487
id_link = A(id, _href=URL(r=request, f="user", args=[id, "read"]))
488
checkbox = INPUT(_type="checkbox", _value="on", _name=id, _class="remove_item")
489
item_list.append(TR(TD(id_link), TD(item_first), TD(item_second), TD(item_description), TD(checkbox), _class=theclass))
491
if auth.settings.username:
492
username_label = T("Username")
494
username_label = T("Email")
495
table_header = THEAD(TR(TH("ID"), TH(T("First Name")), TH(T("Last Name")), TH(username_label), TH(T("Remove"))))
496
table_footer = TFOOT(TR(TD(_colspan=4), TD(INPUT(_id="submit_delete_button", _type="submit", _value=T("Remove")))))
497
items = DIV(FORM(TABLE(table_header, TBODY(item_list), table_footer, _id="table-container"), _name="custom", _method="post", _enctype="multipart/form-data", _action=URL(r=request, f="group_remove_users", args=[group])))
499
subtitle = T("Users")
500
crud.messages.submit_button = T("Add")
501
crud.messages.record_created = T("Role Updated")
502
form = crud.create(table, next=URL(r=request, args=[group]))
503
addtitle = T("Add New User to Role")
504
output.update(dict(subtitle=subtitle, items=items, addtitle=addtitle, form=form))
507
@auth.shn_requires_membership(1)
508
def group_remove_users():
509
"Remove users from a group"
510
if len(request.args) == 0:
511
session.error = T("Need to specify a group!")
512
redirect(URL(r=request, f="group"))
513
group = request.args(0)
514
table = db.auth_membership
515
for var in request.vars:
517
query = (table.group_id == group) & (table.user_id == user)
520
#crud.settings.update_onaccept = lambda form: shn_audit_update(form, "membership", "html")
521
session.flash = T("Users removed")
522
redirect(URL(r=request, f="users", args=[group]))
524
@auth.shn_requires_membership(1)
526
"List/amend which groups a User is in"
529
user = int(request.args(0))
530
except TypeError, ValueError:
531
session.error = T("Need to specify a user!")
532
redirect(URL(r=request, f="user"))
534
table = db.auth_membership
535
query = table.user_id == user
536
title = db.auth_user[user].first_name + " " + db.auth_user[user].last_name
537
description = db.auth_user[user].email
538
# Start building the Return
539
output = dict(title=title, description=description, user=user)
542
crud.settings.create_onaccept = lambda form: shn_audit_create(form, module, "membership", "html")
543
# Many<>Many selection (Deletable, no Quantity)
545
sqlrows = db(query).select()
555
_group = db.auth_group[id]
556
item_first = _group.role
557
item_description = _group.description
558
id_link = A(id, _href=URL(r=request, f="group", args=[id, "read"]))
559
checkbox = INPUT(_type="checkbox", _value="on", _name=id, _class="remove_item")
560
item_list.append(TR(TD(id_link), TD(item_first), TD(item_description), TD(checkbox), _class=theclass))
562
table_header = THEAD(TR(TH("ID"), TH(T("Role")), TH(T("Description")), TH(T("Remove"))))
563
table_footer = TFOOT(TR(TD(_colspan=3), TD(INPUT(_id="submit_delete_button", _type="submit", _value=T("Remove")))))
564
items = DIV(FORM(TABLE(table_header, TBODY(item_list), table_footer, _id="table-container"), _name="custom", _method="post", _enctype="multipart/form-data", _action=URL(r=request, f="user_remove_groups", args=[user])))
566
subtitle = T("Roles")
567
crud.messages.submit_button = T("Add")
568
crud.messages.record_created = T("User Updated")
569
form = crud.create(table, next=URL(r=request, args=[user]))
570
addtitle = T("Add New Role to User")
571
output.update(dict(subtitle=subtitle, items=items, addtitle=addtitle, form=form))
574
@auth.shn_requires_membership(1)
575
def user_remove_groups():
576
"Remove groups from a user"
577
if len(request.args) == 0:
578
session.error = T("Need to specify a user!")
579
redirect(URL(r=request, f="user"))
580
user = request.args(0)
581
table = db.auth_membership
582
for var in request.vars:
584
query = (table.group_id == group) & (table.user_id == user)
587
#crud.settings.update_onaccept = lambda form: shn_audit_update(form, "membership", "html")
588
session.flash = T("Groups removed")
589
redirect(URL(r=request, f="groups", args=[user]))
592
@auth.shn_requires_membership(1)
594
"Import data via POST upload to CRUD controller. Old - being replaced by Sync/Importer."
595
title = T("Import Data")
596
crud.messages.submit_button = "Upload"
599
import_job_form = crud.create(db.admin_import_job)
600
# Tell the CRUD create form to post to a different location
601
import_job_form.custom.begin.text = str(import_job_form.custom.begin).replace(
603
'action="%s"' % URL(r=request, f="import_job", args=["create"]))
605
return dict(title=title,
606
import_job_form=import_job_form)
608
@auth.shn_requires_membership(1)
609
def import_csv_data():
610
"Import CSV data via POST upload to Database."
611
file = request.vars.multifile.file
613
# Assumes that it is a concatenation of tables
615
session.flash = T("Data uploaded")
617
session.error = T("Unable to parse CSV file!")
618
redirect(URL(r=request, f="import_data"))
621
@auth.requires_login()
623
"Export data via CRUD controller. Old - being replaced by Sync."
624
title = T("Export Data")
625
return dict(title=title)
627
@auth.shn_requires_membership(1)
629
"Export entire database as CSV. Old - being replaced by Sync."
631
output = StringIO.StringIO()
633
db.export_to_csv_file(output)
636
import gluon.contenttype
637
response.headers["Content-Type"] = gluon.contenttype.contenttype(".csv")
638
filename = "%s_database.csv" % (request.env.server_name)
639
response.headers["Content-disposition"] = "attachment; filename=%s" % filename
644
# Unstructured Data Import
645
# Deprecated - being replaced by Importer
646
@auth.shn_requires_membership(1)
648
"RESTful CRUD controller to handle 'jobs' for importing unstructured data."
651
resource = "import_job"
652
table = "%s_%s" % (module, resource)
654
jr = s3xrc.request(module, resource, request, session=session)
655
CREATE_NEW_IMPORT_JOB = T("Create New Import Job")
656
LIST_IMPORT_JOBS = ("List Import Jobs")
657
s3.crud_strings[table] = Storage(
658
title_create=CREATE_NEW_IMPORT_JOB,
659
title_display=T("Import Job"),
660
title_list=LIST_IMPORT_JOBS,
661
title_update=T("Update Import Job"),
662
subtitle_create=CREATE_NEW_IMPORT_JOB,
663
subtitle_list=T("Import Jobs"),
664
label_list_button=LIST_IMPORT_JOBS,
665
label_create_button=T("Create Import Job"),
666
msg_record_created=T("Import job created"),
667
msg_list_empty=T("No import jobs")
670
if len(request.args) == 0:
671
return _import_job_list(jr)
672
action = request.args(0)
673
if action == "create":
674
return _import_job_create(jr)
675
if action == "update":
676
return _import_job_update(jr)
678
def _import_job_list(jr):
679
return shn_list(jr, listadd=False)
681
def _import_job_create(jr):
682
if jr.http != "POST":
685
def process_new_file_and_redirect(form):
686
filename = form.vars.source_file_newfilename
687
filepath = os.path.join(request.folder, "uploads", filename)
688
query = (jr.table.id == form.vars.id)
690
model_fields = dict(map(
691
lambda x:(x.lower(), x),
692
get_matchable_fields(form.vars.module, form.vars.resource)))
694
reader = csv.reader(open(filepath, "r"))
698
col_match = col.lower()
699
if col_match in model_fields:
700
# Explicit name match.
701
field = model_fields.pop(col_match)
703
# Partial name patch.
704
for field_name in model_fields:
705
if col_match in field_name or field_name in col_match:
706
field = model_fields.pop(field_name)
708
column_map.append((col, field))
710
pickled_map = pickle.dumps(column_map, pickle.HIGHEST_PROTOCOL)
711
db(query).update(column_map=pickled_map)
713
db(query).update(status="failed")
714
redirect(URL(r=request, f="import_job", args=[form.vars.id, "update"]))
716
form = crud.create(jr.table, onaccept=process_new_file_and_redirect)
717
response.flash = "Form errors!"
718
redirect(URL(r=request, f="import_data"))
720
def _import_job_update(jr):
721
if len(request.args) < 2:
724
query = (jr.table.id == id)
725
job = db(query).select()
727
raise HTTP(404, body=s3xrc.xml.json_message(False, 404, session.error))
731
output = _import_job_update_GET(jr, job)
732
elif jr.http == "POST":
733
output = _import_job_update_POST(jr, job)
735
raise HTTP(400, body=INVALIDREQUEST)
737
title = "%s - %s" % (T("Input Job"), job.description)
738
output.update(dict(title=title, job=job))
739
response.view = "admin/import_job_update.html"
742
def _import_job_update_GET(jr, job):
743
table = db.admin_import_line
744
query = (table.import_job == job.id)
746
if job.status == "new":
748
job.column_map = pickle.loads(job.column_map)
749
except pickle.UnpicklingError:
751
model_fields = get_matchable_fields(job.module, job.resource)
752
return dict(model_fields=model_fields)
754
if job.status == "processing":
755
num_lines = db(query).count()
756
return dict(num_lines=num_lines, update_speed=60)
758
if job.status in ["processed", "failed", "imported"]:
759
def _include_field(f):
760
if f in ["import_job"]:
762
return table[f].readable
764
fields = [table[f] for f in table.fields if _include_field(f)]
765
headers = dict(map(lambda f: (str(f), f.label), fields))
767
db["admin_import_line"],
771
truncate=48, _id="list", _class="display")
772
response.extra_styles = ["admin/import_job.css"]
773
return dict(items=items)
775
if job.status == "import":
776
query = (table.import_job == job.id) & (table.status == "imported")
777
num_lines = db(query).count()
778
return dict(num_lines=num_lines, update_speed=60)
782
def _import_job_update_POST(jr, job):
783
if job.status == "new":
786
column_map = pickle.loads(job.column_map)
787
except pickle.UnpicklingError:
789
for key, value in request.vars.iteritems():
790
if not key.startswith("column_map_"):
793
idx = int(key.split("_")[-1])
796
if value != "None (Ignore)":
797
column_map[idx] = (column_map[idx][0], value)
799
column_map[idx] = (column_map[idx][0], None)
800
jr.table[job.id] = dict(
801
column_map=pickle.dumps(column_map, pickle.HIGHEST_PROTOCOL),
803
job.status = "processing"
805
if job.status == "processed":
806
query = db.admin_import_line.id
807
for var, status in request.vars.iteritems():
808
if not var.startswith("status_"):
811
import_line_id = int(var.split("_")[-1])
814
db(db.admin_import_line.id == import_line_id).update(status=status)
815
jr.table[job.id] = dict(status="import")
816
job.status = "import"
818
return _import_job_update_GET(jr, job)
820
def get_matchable_fields(module, resource):
821
model_fields = getattr(db, "%s_%s" % (module, resource)).fields
822
for name in ["created_on", "modified_on", "id", "uuid", "deleted"]:
823
model_fields.remove(name)
824
model_fields.insert(0, "None (Ignore)")
830
"""Process the POST data returned from Selenium TestRunner.
831
The data is written out to 2 files. The overall results are written to
832
date-time-browserName-metadata.txt as a list of key: value, one per line. The
833
suiteTable and testTables are written to date-time-browserName-results.html.
836
if not request.vars.result:
841
result = request.vars.result
842
totalTime = request.vars.totalTime
843
numberOfSuccesses = request.vars.numTestPasses
844
numberOfFailures = request.vars.numTestFailures
845
numberOfCommandSuccesses = request.vars.numCommandPasses
846
numberOfCommandFailures = request.vars.numCommandFailures
847
numberOfCommandErrors = request.vars.numCommandErrors
850
if request.vars.suite:
851
suiteTable = request.vars.suite
855
while request.vars["testTable.%s" % testTableNum]:
856
testTable = request.vars["testTable.%s" % testTableNum]
857
testTables.append(testTable)
860
request.vars["testTable.%s" % testTableNum]
865
# Unescape the HTML tables
867
suiteTable = urllib.unquote(suiteTable)
868
testTables = map(urllib.unquote, testTables)
870
# We want to store results separately for each browser
871
browserName = getBrowserName(request.env.http_user_agent)
872
date = str(request.utcnow)[:-16]
873
time = str(request.utcnow)[11:-10]
874
time = time.replace(":", "-")
877
outputDir = os.path.join(request.folder, "static", "selenium", "results")
878
metadataFile = "%s-%s-%s-metadata.txt" % (date, time, browserName)
879
dataFile = "%s-%s-%s-results.html" % (date, time, browserName)
881
#xmlText = '<selenium result="' + result + '" totalTime="' + totalTime + '" successes="' + numberOfCommandSuccesses + '" failures="' + numberOfCommandFailures + '" errors="' + numberOfCommandErrors + '" />'
882
f = open(os.path.join(outputDir, metadataFile), "w")
883
for key in request.vars.keys():
884
if "testTable" in key or key in ["log", "suite"]:
887
print >> f, "%s: %s" % (key, request.vars[key])
890
f = open(os.path.join(outputDir, dataFile), "w")
891
print >> f, suiteTable
892
for testTable in testTables:
893
print >> f, "<br/><br/>"
894
print >> f, testTable
897
message = DIV(P("Results have been successfully posted to the server here:"),
898
P(A(metadataFile, _href=URL(r=request, c="static", f="selenium", args=["results", metadataFile]))),
899
P(A(dataFile, _href=URL(r=request, c="static", f="selenium", args=["results", dataFile]))))
901
response.view = "display.html"
902
title = T("Test Results")
903
return dict(title=title, item=message)
905
# Ticket Viewer functions Borrowed from admin application of web2py
906
@auth.shn_requires_membership(1)
908
""" Error handler """
910
app = request.application
912
for item in request.vars:
913
if item[:7] == "delete_":
914
os.unlink(apath("%s/errors/%s" % (app, item[7:]), r=request))
916
func = lambda p: os.stat(apath("%s/errors/%s" % (app, p), r=request)).st_mtime
917
tickets = sorted(listdir(apath("%s/errors/" % app, r=request), "^\w.*"),
921
return dict(app=app, tickets=tickets)
924
""" Create a link from a path """
925
tryFile = path.replace("\\", "/")
927
if os.path.isabs(tryFile) and os.path.isfile(tryFile):
928
(folder, filename) = os.path.split(tryFile)
929
(base, ext) = os.path.splitext(filename)
930
app = request.args[0]
932
editable = {"controllers": ".py", "models": ".py", "views": ".html"}
933
for key in editable.keys():
934
check_extension = folder.endswith("%s/%s" % (app, key))
935
if ext.lower() == editable[key] and check_extension:
936
return A('"' + tryFile + '"',
938
f="edit/%s/%s/%s" % (app, key, filename))).xml()
942
def make_links(traceback):
943
""" Make links using the given traceback """
945
lwords = traceback.split('"')
947
# Make the short circuit compatible with <= python2.4
948
result = (len(lwords) != 0) and lwords[0] or ""
952
while i < len(lwords):
953
link = make_link(lwords[i])
956
result += '"' + lwords[i]
960
if i + 1 < len(lwords):
961
result += lwords[i + 1]
969
class TRACEBACK(object):
970
""" Generate the traceback """
972
def __init__(self, text):
973
""" TRACEBACK constructor """
975
self.s = make_links(CODE(text).xml())
978
""" Returns the xml """
984
@auth.shn_requires_membership(1)
986
""" Ticket handler """
988
if len(request.args) != 2:
989
session.flash = T("Invalid ticket")
990
redirect(URL(r=request))
992
app = request.args[0]
993
ticket = request.args[1]
994
e = RestrictedError()
995
e.load(request, app, ticket)
999
traceback=TRACEBACK(e.traceback),