PageRenderTime 80ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/subsystem/seeddb/src/seeddb.py

https://bitbucket.org/lkarsten/nav-lkarsten
Python | 6511 lines | 6046 code | 169 blank | 296 comment | 298 complexity | 5b7aca96e4146190544e602efba5aa37 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, BSD-3-Clause, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. # -*- coding: ISO8859-1 -*-
  2. # $Id$
  3. #
  4. # Copyright 2003, 2004 Norwegian University of Science and Technology
  5. #
  6. # This file is part of Network Administration Visualized (NAV)
  7. #
  8. # NAV is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # NAV is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with NAV; if not, write to the Free Software
  20. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  21. #
  22. #
  23. # Authors: Hans J?rgen Hoel <hansjorg@orakel.ntnu.no>
  24. #
  25. #################################################
  26. ## Imports
  27. import seeddbTables
  28. import nav.Snmp
  29. import sys
  30. import re
  31. import copy
  32. import initBox
  33. import forgetSQL
  34. import nav.web
  35. from nav.db import manage
  36. import nav.util
  37. from mod_python import util,apache
  38. from seeddbSQL import *
  39. from socket import gethostbyaddr,gethostbyname,gaierror
  40. from nav.web.serviceHelper import getCheckers,getDescription
  41. from nav.web.selectTree import selectTree,selectTreeLayoutBox
  42. from nav.web.selectTree import simpleSelect,updateSelect
  43. # Temporary fix:
  44. mod = __import__('encodings.utf_8',globals(),locals(),'*')
  45. mod = __import__('encodings.utf_16_be',globals(),locals(),'*')
  46. mod = __import__('encodings.latin_1',globals(),locals(),'*')
  47. mod = __import__('encodings.utf_16',globals(),locals(),'*')
  48. #################################################
  49. ## Templates
  50. from nav.web.templates.seeddbTemplate import seeddbTemplate
  51. #################################################
  52. ## Constants
  53. BASEPATH = '/seeddb/'
  54. CONFIGFILE = 'seeddb.conf'
  55. EDITPATH = [('Home','/'), ('Seed Database',BASEPATH)]
  56. ADDNEW_ENTRY = 'addnew_entry'
  57. UPDATE_ENTRY = 'update_entry'
  58. IGNORE_BOX = 'ignore_this_box'
  59. # Bulk import images
  60. BULK_IMG_GREEN = '/images/lys/green.png'
  61. BULK_IMG_YELLOW = '/images/lys/yellow.png'
  62. BULK_IMG_RED = '/images/lys/red.png'
  63. # Bulk import status
  64. BULK_STATUS_OK = 1
  65. BULK_STATUS_YELLOW_ERROR = 2
  66. BULK_STATUS_RED_ERROR = 3
  67. # Bulk fieldname for unspecified fields
  68. BULK_UNSPECIFIED_FIELDNAME = 'excess'
  69. # REQ_TRUE: a required field
  70. # REQ_FALSE: not required
  71. # REQ_NONEMPTY: not required, but don't insert empty field into db
  72. REQ_TRUE = 1
  73. REQ_FALSE = 2
  74. REQ_NONEMPTY = 3
  75. # Fieldtypes
  76. FIELD_STRING = 1
  77. FIELD_INTEGER = 2
  78. #################################################
  79. ## Functions
  80. def handler(req):
  81. ''' mod_python handler '''
  82. path = req.uri
  83. match = re.search('seeddb/(.+)$',path)
  84. if match:
  85. request = match.group(1)
  86. request = request.split('/')
  87. else:
  88. request = ""
  89. # Read configuration file
  90. #if not globals().has_key('CONFIG_CACHED'):
  91. readConfig()
  92. # Get form from request object
  93. keep_blank_values = True
  94. fieldStorage = util.FieldStorage(req,keep_blank_values)
  95. form = {}
  96. for field in fieldStorage.list:
  97. if form.has_key(field.name):
  98. # This input name already exists
  99. if type(form[field.name]) is list:
  100. # and it's already a list, so just append the
  101. # the new value
  102. form[field.name].append(str(field.value))
  103. else:
  104. # it's a string, so make a list and append the
  105. # new value to it
  106. valueList = []
  107. valueList.append(form[field.name])
  108. valueList.append(str(field.value))
  109. form[field.name] = valueList
  110. else:
  111. form[field.name] = str(field.value)
  112. # Check that all input is in the default encoding (utf8)
  113. unicodeError = False
  114. try:
  115. for key,value in form.items():
  116. if type(value) is list:
  117. for field in value:
  118. unicode_str = field.decode(DEFAULT_ENCODING)
  119. else:
  120. unicode_str = value.decode(DEFAULT_ENCODING)
  121. except UnicodeError:
  122. # Some of the input values is not in the default encoding
  123. unicodeError = True
  124. # Set form in request object
  125. req.form = form
  126. output = None
  127. showHelp = False
  128. if len(request) == 2:
  129. if request[0] == 'help':
  130. showHelp = True
  131. request = []
  132. if not len(request) > 1:
  133. output = index(req,showHelp)
  134. else:
  135. table = request[0]
  136. action = request[1]
  137. if table == 'bulk':
  138. output = bulkImport(req,action)
  139. elif pageList.has_key(table):
  140. output = editPage(req,pageList[table](),request,unicodeError)
  141. if output:
  142. req.content_type = "text/html"
  143. req.write(output)
  144. return apache.OK
  145. else:
  146. return apache.HTTP_NOT_FOUND
  147. def readConfig():
  148. ''' Reads configuration from seeddb.conf and sets global
  149. variables. '''
  150. global CONFIG_CACHED,DEFAULT_ENCODING,BULK_TRY_ENCODINGS,\
  151. CATEGORY_LIST,SPLIT_LIST,SPLIT_OPPOSITE
  152. config = nav.config.readConfig(CONFIGFILE)
  153. CONFIG_CACHED = True
  154. DEFAULT_ENCODING = config['default_encoding']
  155. BULK_TRY_ENCODINGS = eval(config['bulk_try_encodings'])
  156. # Make list of cable categories
  157. catlist = eval(config['categories'])
  158. categories = []
  159. for cat in catlist:
  160. categories.append(eval(config[cat]))
  161. CATEGORY_LIST = categories
  162. # Make list of splits and dict of opposite splits
  163. splitlist = eval(config['splits'])
  164. splits = []
  165. opposite = {}
  166. for splitName in splitlist:
  167. split = eval(config[splitName])
  168. # id=0, descr=1, opposite=2
  169. splits.append((split[0],split[1]))
  170. if split[2]:
  171. # References another split, set id
  172. opposite[split[0]] = eval(config[split[2]])[0]
  173. else:
  174. opposite[split[0]] = None
  175. SPLIT_LIST = splits
  176. SPLIT_OPPOSITE = opposite
  177. def index(req,showHelp=False,status=None):
  178. ''' Generates the index page (main menu) '''
  179. # Empty body
  180. class body:
  181. ''' Empty struct for template '''
  182. def __init__(self):
  183. pass
  184. body.status = status
  185. body.title = 'Seed Database - Modify seed information for the NAV database'
  186. body.infotext = 'Here you can add, delete or edit seed information ' +\
  187. 'that are needed for the NAV database. Keep in mind ' +\
  188. 'that most of the data in the NAV database are ' +\
  189. 'collected automatically by NAV background processes.'
  190. body.showHelp = showHelp
  191. body.help = [BASEPATH + 'help/','Show help']
  192. body.nohelp = [BASEPATH,'Hide help']
  193. body.tables = []
  194. headings = []
  195. # Table for boxes and services
  196. rows = [['IP devices',
  197. 'Input seed information on the IP devices you want to ' +\
  198. 'monitor',
  199. [BASEPATH + 'netbox/edit','Add'],
  200. [BASEPATH + 'netbox/list','Edit'],
  201. [BASEPATH + 'bulk/netbox','Bulk import']],
  202. ['Services',
  203. 'Which services on which servers do you want to monitor?',
  204. [BASEPATH + 'service/edit','Add'],
  205. [BASEPATH + 'service/list','Edit'],
  206. [BASEPATH + 'bulk/service','Bulk import']]]
  207. body.tables.append(Table('IP devices and services','',headings,rows))
  208. # Table for rooms and locations
  209. rows = [['Room',
  210. 'Register all wiring closets and server rooms that contain ' +\
  211. 'IP devices which NAV monitors',
  212. [BASEPATH + 'room/edit','Add'],
  213. [BASEPATH + 'room/list','Edit'],
  214. [BASEPATH + 'bulk/room','Bulk import']],
  215. ['Location',
  216. 'Rooms are organised in locations',
  217. [BASEPATH + 'location/edit','Add'],
  218. [BASEPATH + 'location/list','Edit'],
  219. [BASEPATH + 'bulk/location','Bulk import']]]
  220. body.tables.append(Table('Rooms and locations','',headings,rows))
  221. # Table org and usage cat
  222. rows = [['Organisation',
  223. 'Register all organisational units that are relevant. I.e. ' +\
  224. 'all units that have their own subnet/server facilities.',
  225. [BASEPATH + 'org/edit','Add'],
  226. [BASEPATH + 'org/list','Edit'],
  227. [BASEPATH + 'bulk/org','Bulk import']],
  228. ['Usage categories',
  229. 'NAV encourages a structure in the subnet structure. ' +\
  230. 'Typically a subnet has users from an organisational ' +\
  231. 'unit. In addition this may be subdivided into a ' +\
  232. 'category of users, i.e. students, employees, ' +\
  233. 'administration etc.',
  234. [BASEPATH + 'usage/edit','Add'],
  235. [BASEPATH + 'usage/list','Edit'],
  236. [BASEPATH + 'bulk/usage','Bulk import']]]
  237. body.tables.append(Table('Organisation and usage categories','',
  238. headings,rows))
  239. # Table for types, products and vendors
  240. rows = [['Type',
  241. 'The type describes the type of network device, uniquely ' +\
  242. 'described from the SNMP sysobjectID',
  243. [BASEPATH + 'type/edit','Add'],
  244. [BASEPATH + 'type/list','Edit'],
  245. [BASEPATH + 'bulk/type','Bulk import']],
  246. ['Product',
  247. 'Similar to type, but with focus on the product number and ' +\
  248. 'description. A product may be a type, it may also be a ' +\
  249. 'component (i.e. module) within an equipment type',
  250. [BASEPATH + 'product/edit','Add'],
  251. [BASEPATH + 'product/list','Edit'],
  252. [BASEPATH + 'bulk/product','Bulk import']],
  253. ['Vendor',
  254. 'Register the vendors that manufacture equipment that are ' +\
  255. 'represented in your network.',
  256. [BASEPATH + 'vendor/edit','Add'],
  257. [BASEPATH + 'vendor/list','Edit'],
  258. [BASEPATH + 'bulk/vendor','Bulk import']],
  259. ['Snmpoid',
  260. 'Manually add snmpoids (candidates for the cricket collector)',
  261. [BASEPATH + 'snmpoid/edit','Add'],
  262. ['',''],
  263. ['','']],
  264. ['Subcategory',
  265. 'The main categories of a device are predefined by NAV (i.e. ' +\
  266. 'GW,SW,SRV). You may however create subcategories yourself.',
  267. [BASEPATH + 'subcat/edit','Add'],
  268. [BASEPATH + 'subcat/list','Edit'],
  269. [BASEPATH + 'bulk/subcat','Bulk import']],
  270. ]
  271. body.tables.append(Table('Types, products and vendors','',headings,rows))
  272. # Table for vlans and special subnets
  273. rows = [['Vlan',
  274. 'Register the vlan number that are in use (this info may ' +\
  275. 'also be derived automatically from the routers)',
  276. None,
  277. [BASEPATH + 'vlan/list','Edit'],
  278. None],
  279. ['Prefix',
  280. 'Register special ip prefixes. Typically reserved prefixes ' +\
  281. 'or prefixes that are not directly connected to monitored ' +\
  282. 'routers/firewalls fall into this category',
  283. [BASEPATH + 'prefix/edit','Add'],
  284. [BASEPATH + 'prefix/list','Edit'],
  285. [BASEPATH + 'bulk/prefix','Bulk import']]]
  286. body.tables.append(Table('Vlans and special subnets','',headings,rows))
  287. # Table for cabling and patch
  288. rows = [['Cabling',
  289. 'Here you may document the horizontal cabling system ',
  290. [BASEPATH + 'cabling/edit','Add'],
  291. [BASEPATH + 'cabling/list','Edit'],
  292. [BASEPATH + 'bulk/cabling','Bulk import']],
  293. ['Patch',
  294. 'Register the cross connects in the wiring closets ',
  295. [BASEPATH + 'patch/edit','Add'],
  296. [BASEPATH + 'patch/list','Edit'],
  297. [BASEPATH + 'bulk/patch','Bulk import']]]
  298. body.tables.append(Table('Cabling system','',headings,rows))
  299. nameSpace = {'entryList': None, 'editList': None, 'editForm': None, 'body': body}
  300. template = seeddbTemplate(searchList=[nameSpace])
  301. template.path = [('Home','/'),
  302. ('Seed Database',None)]
  303. return template.respond()
  304. ######################
  305. ##
  306. ## General functions
  307. ##
  308. ########################
  309. # General function for handling editing
  310. def editPage(req,page,request,unicodeError):
  311. ''' General handler function for all editpages. Whenever an action
  312. add, update, list or delete is performed, this function is called. '''
  313. # Cancel button redirect
  314. if req.form.has_key(editForm.cnameCancel):
  315. nav.web.redirect(req,BASEPATH,seeOther=True)
  316. # Make a status object
  317. status = seeddbStatus()
  318. # Get action from request (url)
  319. action = request[1]
  320. selected = []
  321. addedId = None
  322. # Get editid from url if it is present (external links and list links)
  323. if len(request) > 1:
  324. if (len(request) == 3) or (len(request) == 4):
  325. if request[2]:
  326. selected = [request[2]]
  327. # Make a list of selected entries from a posted selectlist
  328. if req.form.has_key(selectList.cnameChk):
  329. if type(req.form[selectList.cnameChk]) is str:
  330. # only one selected
  331. selected = [req.form[selectList.cnameChk]]
  332. elif type(req.form[selectList.cnameChk]) is list:
  333. # more than one selected
  334. for s in req.form[selectList.cnameChk]:
  335. selected.append(s)
  336. # Remember entries which we are already editing
  337. # Used if editing is interrupted by an error
  338. if req.form.has_key(UPDATE_ENTRY):
  339. if type(req.form[UPDATE_ENTRY]) is str:
  340. # only one selected
  341. selected = [req.form[UPDATE_ENTRY]]
  342. elif type(req.form[UPDATE_ENTRY]) is list:
  343. # more than one selected
  344. for s in req.form[UPDATE_ENTRY]:
  345. selected.append(s)
  346. # Disallow adding (for pageVlan)
  347. if req.form.has_key(selectList.cnameAdd) and hasattr(page,'disallowAdd'):
  348. status.errors.append(page.disallowAddReason)
  349. action = 'list'
  350. # Check if any entries are selected when action is 'edit' or 'delete'
  351. if action == 'edit':
  352. if req.form.has_key(selectList.cnameEdit):
  353. if not selected:
  354. status.errors.append('No entries selected for editing')
  355. action = 'list'
  356. elif req.form.has_key(selectList.cnameDelete):
  357. action = 'delete'
  358. if not selected:
  359. status.errors.append('No entries selected')
  360. action = 'list'
  361. else:
  362. if not selected:
  363. action = 'add'
  364. # Check for unicode errors in input
  365. if unicodeError:
  366. status.errors.append('The data you input was sent in a ' +\
  367. 'non-recognisible encoding. Make sure your '
  368. 'browser uses automatic character encoding ' +\
  369. 'or set it to \'' + str(DEFAULT_ENCODING) + '\'.')
  370. action = 'list'
  371. # Set 'current path'
  372. path = page.pathAdd
  373. templatebox = page.editbox(page)
  374. # Copy field defintions from the main templatebox (used by add/update)
  375. page.fields = templatebox.fields
  376. # Make form object for template
  377. outputForm = editForm()
  378. if hasattr(page,'action'):
  379. outputForm.action = page.action
  380. else:
  381. outputForm.action = page.basePath + 'edit'
  382. # List definition, get sorting parameter
  383. sort = None
  384. if req.form.has_key('sort'):
  385. sort = req.form['sort']
  386. listView = page.listDef(req,page,sort)
  387. # Check if the confirm button has been pressed
  388. if req.form.has_key(outputForm.cnameConfirm):
  389. missing = templatebox.hasMissing(req)
  390. if not missing:
  391. status = templatebox.verifyFields(req,status)
  392. if not len(status.errors):
  393. if req.form.has_key(ADDNEW_ENTRY):
  394. # add new entry
  395. (status,action,outputForm,addedId) = page.add(req,
  396. outputForm,action)
  397. elif req.form.has_key(UPDATE_ENTRY):
  398. # update entry
  399. (status,action,outputForm,selected) = page.update(req,
  400. outputForm,
  401. selected)
  402. else:
  403. status.errors.append("Required field '" + missing + "' missing")
  404. # Confirm delete pressed?
  405. elif req.form.has_key(selectList.cnameDeleteConfirm):
  406. status = page.delete(selected,status)
  407. outputForm = None
  408. selected = None
  409. action = 'list'
  410. # Decide what to show
  411. if action == 'predefined':
  412. # Action is predefined by addNetbox() or updateNetbox()
  413. outputForm.textConfirm = 'Continue'
  414. outputForm.action = action
  415. outputForm.status = status
  416. listView = None
  417. elif action == 'edit':
  418. path = page.pathEdit
  419. title = 'Edit '
  420. if len(selected) > 1:
  421. title += page.plural
  422. else:
  423. title += page.singular
  424. outputForm.title = title
  425. outputForm.action = action
  426. outputForm.status = status
  427. outputForm.textConfirm = 'Update'
  428. if page.editMultipleAllowed:
  429. # This page can edit multiple entries at a time
  430. for s in selected:
  431. outputForm.add(page.editbox(page,req,s,formData=req.form))
  432. else:
  433. # This page can only edit one entry at a time (eg. netbox)
  434. outputForm.add(page.editbox(page,req,selected[0],formData=req.form))
  435. # preserve path
  436. #outputForm.action = page.basePath + 'edit/' + selected[0]
  437. listView = None
  438. elif action == 'add':
  439. path = page.pathAdd
  440. outputForm.action = action
  441. outputForm.status = status
  442. outputForm.title = 'Add ' + page.singular
  443. outputForm.textConfirm = 'Add ' + page.singular
  444. outputForm.add(page.editbox(page,req,formData=req.form))
  445. listView = None
  446. elif action == 'delete':
  447. path = page.pathDelete
  448. listView = page.listDef(req,page,sort,selected)
  449. listView.status = status
  450. listView.fill(req)
  451. outputForm = None
  452. elif action == 'list':
  453. if addedId:
  454. listView.selectedId = addedId
  455. if selected:
  456. listView.selectedId = selected[0]
  457. path = page.pathList
  458. listView.status=status
  459. listView.fill(req)
  460. outputForm = None
  461. elif action == 'redirect':
  462. # Redirect to main page (snmpoid add)
  463. return index(req,status=status)
  464. nameSpace = {'entryList': listView,'editList': None,'editForm': outputForm}
  465. template = seeddbTemplate(searchList=[nameSpace])
  466. template.path = path
  467. return template.respond()
  468. def insertNetbox(ip,sysname,catid,roomid,orgid,
  469. ro,rw,deviceid,serial,
  470. typeid,snmpversion,subcatlist=None,
  471. function=None):
  472. ''' Inserts a netbox into the database. Used by pageNetbox.add(). '''
  473. if not deviceid:
  474. # Make new device first
  475. if len(serial):
  476. fields = {'serial': serial}
  477. else:
  478. # Don't insert an empty serialnumber (as serialnumbers must be
  479. # unique in the database) (ie. don't insert '' for serial)
  480. fields = {}
  481. deviceid = addEntryFields(fields,
  482. 'device',
  483. ('deviceid','device_deviceid_seq'))
  484. fields = {'ip': ip,
  485. 'roomid': roomid,
  486. 'deviceid': deviceid,
  487. 'sysname': sysname,
  488. 'catid': catid,
  489. 'orgid': orgid,
  490. 'ro': ro,
  491. 'rw': rw}
  492. #uptodate = false per default
  493. # Get prefixid
  494. query = "SELECT prefixid FROM prefix WHERE '%s'::inet << netaddr" \
  495. % (fields['ip'],)
  496. try:
  497. result = executeSQLreturn(query)
  498. fields['prefixid'] = str(result[0][0])
  499. except:
  500. pass
  501. if typeid:
  502. fields['typeid'] = typeid
  503. # Set uptyodate = false
  504. # This part is done in netbox now. And for a new box this
  505. # field defaults to 'f'
  506. #tifields = {'uptodate': 'f'}
  507. #updateEntryFields(tifields,'type','typeid',typeid)
  508. if snmpversion:
  509. # Only use the first char from initbox, can't insert eg. '2c' in
  510. # this field
  511. snmpversion = snmpversion[0]
  512. fields['snmp_version'] = snmpversion
  513. netboxid = addEntryFields(fields,
  514. 'netbox',
  515. ('netboxid','netbox_netboxid_seq'))
  516. # If subcatlist and function is given, insert them
  517. if subcatlist:
  518. if type(subcatlist) is list:
  519. for sc in subcatlist:
  520. fields = {'netboxid': netboxid,
  521. 'category': sc}
  522. addEntryFields(fields,'netboxcategory')
  523. else:
  524. fields = {'netboxid': netboxid,
  525. 'category': subcatlist}
  526. addEntryFields(fields,'netboxcategory')
  527. if function:
  528. fields = {'netboxid': netboxid,
  529. 'key': '',
  530. 'var': 'function',
  531. 'val': function}
  532. addEntryFields(fields,'netboxinfo')
  533. ######################
  534. ##
  535. ## General classes
  536. ##
  537. ########################
  538. class Table:
  539. ''' A general class for html tables used by index(). '''
  540. def __init__(self,title,infotext,headings,rows):
  541. self.title = title
  542. self.infotext = infotext
  543. self.headings = headings
  544. self.rows = rows
  545. class seeddbStatus:
  546. ''' Struct class which holds two lists (messages and errors). Every
  547. form object got an instance of this class and uses it to add
  548. messages and errors which is then displayed by the template. '''
  549. # List of status messages, one line per message
  550. messages = []
  551. # List of error messages, one line per message
  552. errors = []
  553. def __init__(self):
  554. self.messages = []
  555. self.errors = []
  556. class entryListCell:
  557. ''' Represents a cell (TD) in a selectlist object. '''
  558. CHECKBOX = 'chk'
  559. RADIO = 'rad'
  560. HIDDEN = 'hid'
  561. def __init__(self,text=None,url=None,buttonType=None,
  562. image=None,tooltip=None):
  563. self.text = text
  564. self.url = url
  565. self.buttonType = buttonType
  566. self.image = image
  567. self.tooltip = tooltip
  568. ## class entryList (rename to selectList)
  569. class entryList:
  570. ''' Flexible class for making lists of entries which can be selected.
  571. Used by all 'edit' pages.
  572. Uses the list definitions defined in every page class.
  573. descriptionFormat = [(text,forgetSQLfield),(text,...] '''
  574. # Constants
  575. CNAME_SELECT = 'checkbox_id'
  576. CNAME_ADD = 'submit_add'
  577. CNAME_EDIT = 'submit_edit'
  578. CNAME_DELETE = 'submit_delete'
  579. CNAME_CONFIRM_DELETE = 'confirm_delete'
  580. CNAME_CANCEL = 'form_cancel'
  581. # Class variables used by the template
  582. title = None
  583. status = None
  584. body = None
  585. formMethod = 'post'
  586. formAction = None
  587. selectCname = CNAME_SELECT
  588. buttonsTop = [(CNAME_ADD,'Add new'),
  589. (CNAME_EDIT,'Edit selected'),
  590. (CNAME_DELETE,'Delete selected')]
  591. buttonsBottom = buttonsTop
  592. hideFirstHeading = False # Don't show first column heading (usually
  593. # the select heading) if True
  594. buttonTypeOverride = None # override the chosen select button type
  595. # used for bulk and delete lists where there
  596. # is no checkbox/radiobut., only hidden
  597. headings = [] # list of cell objects
  598. rows = [] # tuples of (sortstring,id,cell object)
  599. # Variables for filling the list
  600. tableName = None
  601. basePath = None
  602. sortBy = None # Sort by columnumber
  603. defaultSortBy = None # Default columnnumber sorted by
  604. headingDefinition = None # list of tuples (heading,show sort link)
  605. cellDefintion = None # cellDefinition list
  606. where = None # List of id's (strings)
  607. sortingOn = True # Show links for sorting the list
  608. # SQL filters
  609. filters = None
  610. filterConfirm = 'cn_filter'
  611. filterConfirmText = 'Update list'
  612. selectedId = None
  613. def __init__(self,req,struct,sort,deleteWhere=None):
  614. self.headings = []
  615. self.rows = []
  616. if sort:
  617. sort = int(sort)
  618. self.sortBy = sort
  619. self.tableName = struct.tableName
  620. self.tableIdKey = struct.tableIdKey
  621. self.basePath = struct.basePath
  622. self.deleteWhere = deleteWhere
  623. self.formAction = self.basePath + 'edit'
  624. self.filterAction = self.basePath + 'list'
  625. if deleteWhere:
  626. self.buttonTypeOverride = entryListCell.HIDDEN
  627. self.hideFirstHeading = True
  628. self.where = deleteWhere
  629. title = 'Are you sure you want to delete the ' + \
  630. 'selected '
  631. if len(deleteWhere) > 1:
  632. title += struct.plural
  633. else:
  634. title += struct.singular
  635. self.title = title + '?'
  636. self.sortingOn = False
  637. self.buttonsTop = None
  638. self.buttonsBottom = [(self.CNAME_CONFIRM_DELETE, 'Delete'),
  639. (self.CNAME_CANCEL, 'Cancel')]
  640. else:
  641. self.title = 'Edit ' + struct.plural
  642. self.sortingOn = True
  643. def fill(self,req):
  644. """ Fill the list with data from the database. """
  645. # No filters if this is a delete list
  646. if self.deleteWhere:
  647. self.filters = None
  648. # Make filters
  649. if self.filters:
  650. self.makeFilters(req)
  651. # Make headings
  652. i = 0
  653. for heading,sortlink,sortFunction in self.headingDefinition:
  654. if self.hideFirstHeading:
  655. heading = ''
  656. self.hideFirstHeading = False
  657. if self.sortBy:
  658. currentOrder = self.sortBy
  659. else:
  660. currentOrder = self.defaultSortBy
  661. s = i
  662. if i == currentOrder:
  663. # Reverse sort?
  664. s = -i
  665. url = self.basePath + 'list?sort=' + str(s)
  666. if sortlink and self.sortingOn:
  667. self.headings.append(entryListCell(heading,
  668. url))
  669. else:
  670. self.headings.append(entryListCell(heading,
  671. None))
  672. i = i + 1
  673. # Check filters and decide if we're going to show list
  674. renderList = True
  675. filterSettings = []
  676. if self.filters:
  677. renderList = False
  678. for filter in self.filters:
  679. # filter[10] is selected id in filter
  680. if filter[10]:
  681. # Something is selected, show list
  682. renderList = True
  683. # filter[6] is tableIdKey
  684. filterSettings.append((filter[10],filter[6]))
  685. # Skip filling if no result from filter
  686. if renderList:
  687. # Preparse tooltips, etc.
  688. for sqlQuery,definition in self.cellDefinition:
  689. for column in definition:
  690. for cell in column:
  691. if type(cell) is list:
  692. # This cell definition is a list, as opposed to
  693. # a tuple, so we must prefetch some data for the
  694. # parse function
  695. # Must prefetch data for this column
  696. for tooltipDef in cell:
  697. # There can be one or more defintions per cell
  698. sql = tooltipDef[0]
  699. # column[2] is reserved for data
  700. tooltipDef[2] = executeSQLreturn(sql)
  701. # Make rows
  702. reverseSort = False
  703. if self.sortBy:
  704. if self.sortBy < 0:
  705. self.sortBy = self.sortBy * -1
  706. reverseSort = True
  707. for sqlTuple,definition in self.cellDefinition:
  708. # Create SQL query from tuple
  709. columns,tablenames,join,where,orderBy = sqlTuple
  710. sqlQuery = 'SELECT ' + columns + ' FROM ' + tablenames
  711. if join:
  712. sqlQuery += ' %s ' % (join,)
  713. if where:
  714. sqlQuery += ' WHERE ' + where
  715. # Add where clause if self.where is present
  716. if self.where:
  717. if not where:
  718. # No where defined in sqlTuple, so add it now
  719. sqlQuery += ' WHERE '
  720. else:
  721. # Else, these are additional so add AND
  722. sqlQuery += ' AND '
  723. first = True
  724. sqlQuery += ' ('
  725. for id in self.where:
  726. if not first:
  727. sqlQuery += 'OR'
  728. sqlQuery += " %s.%s='%s' " % (self.tableName,
  729. self.tableIdKey,id)
  730. if first:
  731. first = False
  732. sqlQuery += ') '
  733. # Add where clause if filterSettings is present
  734. for filter in filterSettings:
  735. if not where:
  736. # No where defined in sqlTuple, so add it now
  737. sqlQuery += ' WHERE '
  738. else:
  739. # Else, these are additional so add AND
  740. sqlQuery += ' AND '
  741. sqlQuery += filter[1] + "='" + filter[0] + "' "
  742. if orderBy:
  743. sqlQuery += ' ORDER BY ' + orderBy
  744. fetched = executeSQLreturn(sqlQuery)
  745. for row in fetched:
  746. id = row[0]
  747. cells = []
  748. for text,url,buttonType,image,tooltip in definition:
  749. if buttonType and self.buttonTypeOverride:
  750. buttonType = self.buttonTypeOverride
  751. cells.append(entryListCell(self.parse(text,row),
  752. self.parse(url,row,True),
  753. buttonType,
  754. image,
  755. self.parse(tooltip,row)))
  756. sortKey = None
  757. if self.sortBy:
  758. sortKey = row[self.sortBy]
  759. self.rows.append([sortKey,(id,cells)])
  760. if self.sortBy:
  761. if self.headingDefinition[self.sortBy][2]:
  762. # Optional compare method
  763. self.rows.sort(self.headingDefinition[self.sortBy][2])
  764. else:
  765. self.rows.sort()
  766. if reverseSort:
  767. self.rows.reverse()
  768. def parse(self,parseString,currentRow,url=False):
  769. """ Parses format strings used by the list definitions. """
  770. result = None
  771. if type(parseString) is int:
  772. # parseString refers to integer column
  773. result = [currentRow[parseString]]
  774. elif type(parseString) is str:
  775. parseString = parseString.replace('{p}',self.basePath)
  776. parseString = parseString.replace('{id}',str(currentRow[0]))
  777. parseString = parseString.replace('{descr}',str(currentRow[1]))
  778. if parseString.find('SELECT') == 0:
  779. # This string is a sql query (used by vlan)
  780. resultString = ''
  781. sqlresult = executeSQLreturn(parseString)
  782. for row in sqlresult:
  783. for col in row:
  784. resultString += col + '<br>'
  785. result = [resultString]
  786. else:
  787. if url:
  788. result = parseString
  789. else:
  790. result = [parseString]
  791. elif type(parseString) is list:
  792. result = []
  793. for tooltipDef in parseString:
  794. data = tooltipDef[2]
  795. if data:
  796. # There is preparsed data (ie. the sql returned something)
  797. result.append(tooltipDef[1][0])
  798. # [1][0] = Tooltip (can also be other preparsed data) header
  799. for row in data:
  800. if currentRow[0] == row[0]:
  801. # ID's match
  802. result.append(row[1])
  803. return result
  804. def makeFilters(self,req):
  805. for filter in self.filters:
  806. text,firstEntry,sqlTuple,idFormat,optionFormat,\
  807. controlName,tableId,table,fields = filter
  808. optionsList = []
  809. if firstEntry:
  810. firstEntry = ([firstEntry[0]],[firstEntry[1]])
  811. optionsList.append(firstEntry)
  812. # Make sql
  813. sql = "SELECT " + sqlTuple[0] + " FROM " + sqlTuple[1] + " "
  814. if sqlTuple[2]:
  815. sql += sqlTuple[2] + " "
  816. if sqlTuple[3]:
  817. sql += "WHERE " + sqlTuple[3] + " "
  818. if sqlTuple[4]:
  819. sql += "ORDER BY " + sqlTuple[4]
  820. result = executeSQLreturn(sql)
  821. for row in result:
  822. id = self.parse(idFormat,row)
  823. text = self.parse(optionFormat,row)
  824. optionsList.append((id,text))
  825. filter.append(optionsList)
  826. # Selected id
  827. selected = None
  828. if self.selectedId:
  829. entry = table(self.selectedId)
  830. fieldList = fields.split('.')
  831. for field in fieldList:
  832. entry = getattr(entry,field)
  833. selected = str(entry)
  834. if req.form.has_key(controlName):
  835. if len(req.form[controlName]):
  836. selected = req.form[controlName]
  837. filter.append(selected)
  838. # Class representing a form, used by the template
  839. class editForm:
  840. """ Class representing a form element, the main component of every
  841. edit page. Each form element can have any number of editbox
  842. objects added to it. """
  843. # For the template
  844. method = 'post'
  845. action = None
  846. title = None
  847. error = None
  848. status = None
  849. backlink = None
  850. enctype = 'application/x-www-form-urlencoded'
  851. # Text and controlname
  852. textConfirm = None
  853. cnameConfirm = 'form_confirm'
  854. showConfirm = True
  855. textCancel = 'Cancel'
  856. cnameCancel = 'form_cancel'
  857. showCancel = True
  858. actionCancel = BASEPATH
  859. # Only used for netboxes (see template)
  860. # 'submit_delete' is fetched by the same handler as when deleting multiple
  861. # entities from a list
  862. textDelete = 'Delete'
  863. cnameDelete = 'submit_delete'
  864. showDelete = True
  865. # Used by edit netbox in the intermediate
  866. CNAME_CONTINUE = 'cname_continue'
  867. # List of editboxes to display
  868. editboxes = []
  869. def __init__(self,cnameConfirm=None):
  870. if cnameConfirm:
  871. self.cnameConfirm = cnameConfirm
  872. self.editboxes = []
  873. def add(self,box):
  874. """ Add an editbox object to this form element. """
  875. self.editboxes.append(box)
  876. class inputText:
  877. """ Class representing a textinput html control. """
  878. type = 'text'
  879. name = None
  880. maxlength = None
  881. def __init__(self,value='',size=22,maxlength=None,disabled=False):
  882. self.value = value
  883. self.disabled = disabled
  884. self.size = str(size)
  885. if maxlength:
  886. self.maxlength = str(maxlength)
  887. class inputTreeSelect:
  888. """ Container class for treeselects. Used to get treeselect in the
  889. same format as all the other inputs used by the template. """
  890. type = 'treeselect'
  891. name = None
  892. treeselect = None
  893. disabled = False
  894. def __init__(self,treeselect):
  895. self.value = ''
  896. self.treeselect = treeselect
  897. class inputSelect:
  898. """ Class representing a select input html control. """
  899. type = 'select'
  900. name = None
  901. def __init__(self,options=None,table=None,attribs=None,disabled=False):
  902. self.value = ''
  903. self.options = options
  904. self.attribs = attribs
  905. self.disabled = disabled
  906. if table:
  907. self.options = table.getOptions()
  908. class inputMultipleSelect:
  909. """ Class representing a multiple select input html control. """
  910. type = 'multipleselect'
  911. name = None
  912. value = []
  913. def __init__(self,options=None,table=None,disabled=False):
  914. self.options = options
  915. self.disabled = disabled
  916. if table:
  917. self.options = table.getOptions()
  918. class inputFile:
  919. """ Class representing a file upload input control. """
  920. type = 'file'
  921. name = None
  922. value = ''
  923. disabled = False
  924. def __init__(self):
  925. pass
  926. class inputTextArea:
  927. """ Class representing a textarea input html control. """
  928. type = 'textarea'
  929. name = None
  930. def __init__(self,rows=20,cols=80):
  931. self.rows = rows
  932. self.cols = cols
  933. self.value = ''
  934. self.disabled = False
  935. class inputCheckbox:
  936. """ Class representing a checkbox input html control. """
  937. type = 'checkbox'
  938. name = None
  939. def __init__(self,disabled=False):
  940. self.value = '0'
  941. self.disabled = disabled
  942. class inputHidden:
  943. """ Class representing a hidden input html control. """
  944. type = 'hidden'
  945. name = None
  946. disabled = False
  947. def __init__(self,value):
  948. self.value = value
  949. class inputServiceProperties:
  950. """ Contains a list of inputServiceProperty inputs.
  951. (used by pageService) """
  952. type = 'serviceproperties'
  953. disabled = False
  954. def __init__(self,propertyList):
  955. self.propertyList = propertyList
  956. class inputServiceProperty:
  957. """ Class representing a serviceproperty input box.
  958. (used by pageService) """
  959. type = 'serviceproperty'
  960. disabled = False
  961. def __init__(self,title,id,args,optargs,display=True):
  962. self.title = title
  963. self.id = id
  964. self.display = display
  965. self.args = args
  966. self.optargs = optargs
  967. class editbox:
  968. """ Parent class for all the different editboxes which are all added
  969. to an editform object. There are normally one editbox per page, but
  970. for some pages there are more (there are created three editboxes for
  971. the netbox page for example, editbox(main),editboxserial and
  972. editboxfunction (which also includes the subcat)).
  973. The editbox contains field defitions used by the template to render
  974. the forms and functions to fill the form with data from either the
  975. database or from a previous http post. """
  976. boxName = ADDNEW_ENTRY
  977. boxId = 0
  978. # Current box number keeps track of the box number when there are
  979. # more than one editbox in a form. Used by formFill to get correct data
  980. #currentBoxNumber = 0
  981. def fill(self):
  982. """ Fill this form with data from the database (entry = editId). """
  983. entry = self.table(self.editId)
  984. # Set the name of this box to reflect that we are
  985. # updating an entry
  986. self.boxName = UPDATE_ENTRY
  987. self.boxId = self.editId
  988. ## TEMPORARY:
  989. ## check if this is one of the new pages by checking
  990. ## for three instead of two entries in the desc list
  991. if len(self.fields[self.fields.keys()[0]]) > 2:
  992. # Uses psycopg to fill field values
  993. page = pageList[self.page]
  994. select = ''
  995. first = True
  996. keyNumber = {}
  997. i = 0
  998. for key in self.fields.keys():
  999. keyNumber[key] = i
  1000. i+=1
  1001. if not first:
  1002. select += ', '
  1003. select += key
  1004. first = False
  1005. tables = page.tableName
  1006. where = page.tableIdKey + "='" + self.editId + "'"
  1007. # For the benefit of pagePrefix (which must select from vlan too)
  1008. if hasattr(self,'additionalSQL'):
  1009. tables += ', vlan'
  1010. where += self.additionalSQL
  1011. sql = "SELECT %s FROM %s WHERE %s" % (select, tables, where)
  1012. result = executeSQLreturn(sql)
  1013. result = result[0]
  1014. for key,desc in self.fields.items():
  1015. value = result[keyNumber[key]]
  1016. if value:
  1017. desc[0].value = str(value)
  1018. else:
  1019. # Old style filling with forgetsql
  1020. for fieldname,desc in self.fields.items():
  1021. value = getattr(entry,fieldname)
  1022. if value:
  1023. desc[0].value = str(value)
  1024. def setControlNames(self,controlList=None):
  1025. """ Set controlnames for the inputs to the same as the fieldnames. """
  1026. if not controlList:
  1027. controlList = self.fields
  1028. for fieldname,desc in controlList.items():
  1029. desc[0].name = fieldname
  1030. def verifyFields(self,req,status):
  1031. """ Verify that data entered into fields are of correct type.
  1032. Eg. integers in FIELD_INTEGER fields. """
  1033. for field,desc in self.fields.items():
  1034. if req.form.has_key(field):
  1035. if type(req.form[field]) is list:
  1036. # Editing several entries
  1037. for each in req.form[field]:
  1038. # Do tests here
  1039. if desc[3] == FIELD_INTEGER:
  1040. if len(each):
  1041. try:
  1042. int(each)
  1043. except ValueError:
  1044. error = "Invalid integer: '" +\
  1045. str(each) + "'"
  1046. status.errors.append(error)
  1047. else:
  1048. # Editing only one field
  1049. # Do tests here
  1050. if desc[3] == FIELD_INTEGER:
  1051. try:
  1052. if len(req.form[field]):
  1053. int(req.form[field])
  1054. except ValueError:
  1055. error = "Invalid integer: '" + \
  1056. str(req.form[field]) + "'"
  1057. status.errors.append(error)
  1058. return status
  1059. def hasMissing(self,req):
  1060. """ Check if any of the required fields are missing in the req.form
  1061. Returns the name the first missing field, or False
  1062. Note: keep_blank_values (mod_python) must be True or empty fields
  1063. won't be present in the form """
  1064. missing = False
  1065. for field,desc in self.fields.items():
  1066. # Keep blank values must be switched on, or else the next line
  1067. # will fail, could be more robust
  1068. if req.form.has_key(field):
  1069. if type(req.form[field]) is list:
  1070. # the field is a list, several entries have been edited
  1071. for each in req.form[field]:
  1072. if desc[1] == REQ_TRUE:
  1073. # this field is required
  1074. if not len(each):
  1075. if len(desc) > 2:
  1076. # desc[2] is real fieldname
  1077. missing = desc[2]
  1078. else:
  1079. # cryptic fieldname (remove this later)
  1080. missing = field
  1081. break
  1082. else:
  1083. if desc[1] == REQ_TRUE:
  1084. # tihs field is required
  1085. if not len(req.form[field]):
  1086. if len(desc) > 2:
  1087. # desc[2] is real fieldname
  1088. missing = desc[2]
  1089. else:
  1090. # cryptic fieldname (remove this later)
  1091. missing = field
  1092. break
  1093. return missing
  1094. def addHidden(self,fieldname,value):
  1095. """ Add hidden html input control to the editbox. """
  1096. self.hiddenFields[fieldname] = [inputHidden(value),False]
  1097. self.hiddenFields[fieldname][0].name = fieldname
  1098. def addDisabled(self):
  1099. """ Since fields which are disabled, aren't posted (stupid HTML)
  1100. we must add them as hidden fields.
  1101. This only goes for textinputs (?!) so we must also change
  1102. controlnames to avoid getting double values for selects, etc. """
  1103. for fieldname,definition in self.fields.items():
  1104. if definition[0].disabled and (not definition[0].type=='hidden'):
  1105. self.addHidden(fieldname,definition[0].value)
  1106. definition[0].name = definition[0].name + '_disabled'
  1107. def formFill(self,formData):
  1108. """ Fill this editbox with data from the form.
  1109. This is used by intermediate steps (like register serial)
  1110. to remember field values and for refilling a form if an error
  1111. is encountered and the user has to resubmit a form. """
  1112. if not hasattr(editbox,'currentBoxNumber'):
  1113. editbox.currentBoxNumber = 0
  1114. for field,definition in self.fields.items():
  1115. first = True
  1116. numberOfBoxes = None
  1117. if formData.has_key(field):
  1118. if type(formData[field]) is list:
  1119. # Remember how many editboxes this form has
  1120. if first:
  1121. # NB! ASSUMES THAT THE FIRST FIELDNAME IN A FORM
  1122. # IS NEVER A CHECKBOX (SINCE UNCHECKED CHECCKBOXES
  1123. # ARE NOT POSTED). IF IT IS, THEN numberOfBoxes
  1124. # WILL BE ONE LESS THAN IT SHOULD BE FOR EACH
  1125. # UNCHECKED CHECKBOX
  1126. numberOfBoxes = len(formData[field])
  1127. first = False
  1128. # We are editing more than one entry, pick the
  1129. # right data from the form
  1130. definition[0].value = formData[field][editbox.currentBoxNumber]
  1131. else:
  1132. definition[0].value = formData[field]
  1133. # Update class variable currentFormNumber
  1134. if numberOfBoxes:
  1135. editbox.currentBoxNumber +=1
  1136. if editbox.currentBoxNumber == numberOfBoxes:
  1137. # Reset the currentFormNumber class instance
  1138. # Since it is a class instance, it will be common
  1139. # to all editbox instances for all requests, that's
  1140. # why it has to be reset
  1141. editbox.currentBoxNumber = 0
  1142. class editboxHiddenOrMessage(editbox):
  1143. """ This editbox can display a message and contain hidden inputs. """
  1144. page = 'hiddenormessage'
  1145. def __init__(self,message=None):
  1146. self.hiddenFields = {}
  1147. self.message = message
  1148. # The editboxNetbox has UPDATE_ENTRY (which holds the id) or ADDNEW,
  1149. # don't need to repeat it here (so setting boxname to IGNORE_BOX)
  1150. self.boxName = IGNORE_BOX
  1151. class seeddbPage:
  1152. """ The main editing class. Every edit page inherits from this class.
  1153. Contains functions for adding, updating and describing entries.
  1154. Default functions can be overriden by children to do more specific
  1155. handling of adding or updating.
  1156. The children of this class contains all the information needed
  1157. for handling the different tables.
  1158. class seeddbPage
  1159. |+-- class listDef
  1160. |+-- class editbox
  1161. |+-- (optionally more editboxes)
  1162. |+-- def add
  1163. |+-- def update
  1164. |+-- def delete
  1165. |+-- def describe
  1166. """
  1167. def add(self,req,outputForm,action):
  1168. """ Called when 'Add' is clicked on an edit page.
  1169. Takes the formdata from the request object and inserts
  1170. an entry in the database.
  1171. This is the general function used by almost all the edit
  1172. pages. Some of the edit page classes overrides this function
  1173. for more control over the adding (eg. the netbox page).
  1174. req: request object containing a form
  1175. outputForm: form with editboxes
  1176. manipulated directly by eg. editNetbox.add() """
  1177. error = None
  1178. status = seeddbStatus()
  1179. id = None
  1180. nextId = None
  1181. if self.sequence:
  1182. # Get next id from sequence, will need this id to reload
  1183. # entry when making a description of the inserted row
  1184. # In the case that sequence=None, idfield is already
  1185. # present in the field data
  1186. sql = "SELECT nextval('%s')" % (self.sequence,)
  1187. result = executeSQLreturn(sql)
  1188. nextId = str(result[0][0])
  1189. sql = 'INSERT INTO ' + self.tableName + ' ('
  1190. first = True
  1191. for field,descr in self.fields.items():
  1192. if req.form.has_key(field):
  1193. if len(req.form[fie

Large files files are truncated, but you can click here to view the full file