PageRenderTime 936ms CodeModel.GetById 59ms RepoModel.GetById 21ms app.codeStats 2ms

/subsystem/seeddb/src/seeddb.py

https://bitbucket.org/lkarsten/nav-3.6-lkarsten
Python | 6230 lines | 5625 code | 239 blank | 366 comment | 387 complexity | e4b418fa3494a11c2e47a951ae8ef2ce MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0, AGPL-1.0

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

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

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