comparison roundup/cgi/client.py @ 1041:c28603c9f831

Class help and generic class editing done.
author Richard Jones <richard@users.sourceforge.net>
date Wed, 04 Sep 2002 04:31:51 +0000
parents c3e391d9c4e9
children a0c7df67dd9c
comparison
equal deleted inserted replaced
1040:4316d1432db4 1041:c28603c9f831
1 # $Id: client.py,v 1.10 2002-09-03 07:42:38 richard Exp $ 1 # $Id: client.py,v 1.11 2002-09-04 04:31:51 richard Exp $
2 2
3 __doc__ = """ 3 __doc__ = """
4 WWW request handler (also used in the stand-alone server). 4 WWW request handler (also used in the stand-alone server).
5 """ 5 """
6 6
108 try: 108 try:
109 # make sure we're identified (even anonymously) 109 # make sure we're identified (even anonymously)
110 self.determine_user() 110 self.determine_user()
111 # figure out the context and desired content template 111 # figure out the context and desired content template
112 self.determine_context() 112 self.determine_context()
113 # possibly handle a form submit action (may change self.message 113 # possibly handle a form submit action (may change self.message,
114 # and self.template_name) 114 # self.classname and self.template)
115 self.handle_action() 115 self.handle_action()
116 # now render the page 116 # now render the page
117 self.write(self.template('page', ok_message=self.ok_message, 117 self.write(self.renderTemplate('page', '', ok_message=self.ok_message,
118 error_message=self.error_message)) 118 error_message=self.error_message))
119 except Redirect, url: 119 except Redirect, url:
120 # let's redirect - if the url isn't None, then we need to do 120 # let's redirect - if the url isn't None, then we need to do
121 # the headers, otherwise the headers have been set before the 121 # the headers, otherwise the headers have been set before the
122 # exception was raised 122 # exception was raised
125 except SendFile, designator: 125 except SendFile, designator:
126 self.serve_file(designator) 126 self.serve_file(designator)
127 except SendStaticFile, file: 127 except SendStaticFile, file:
128 self.serve_static_file(str(file)) 128 self.serve_static_file(str(file))
129 except Unauthorised, message: 129 except Unauthorised, message:
130 self.write(self.template('page.unauthorised', 130 self.write(self.renderTemplate('page', '', error_message=message))
131 error_message=message))
132 except: 131 except:
133 # everything else 132 # everything else
134 self.write(cgitb.html()) 133 self.write(cgitb.html())
135 134
136 def determine_user(self): 135 def determine_user(self):
205 which defaults to: 204 which defaults to:
206 only classname suplied: "index" 205 only classname suplied: "index"
207 full item designator supplied: "item" 206 full item designator supplied: "item"
208 207
209 We set: 208 We set:
210 self.classname 209 self.classname - the class to display, can be None
211 self.nodeid 210 self.template - the template to render the current context with
212 self.template_name 211 self.nodeid - the nodeid of the class we're displaying
213 ''' 212 '''
214 # default the optional variables 213 # default the optional variables
215 self.classname = None 214 self.classname = None
216 self.nodeid = None 215 self.nodeid = None
217 216
218 # determine the classname and possibly nodeid 217 # determine the classname and possibly nodeid
219 path = self.split_path 218 path = self.split_path
220 if not path or path[0] in ('', 'home', 'index'): 219 if not path or path[0] in ('', 'home', 'index'):
221 if self.form.has_key(':template'): 220 if self.form.has_key(':template'):
222 self.template_type = self.form[':template'].value 221 self.template = self.form[':template'].value
223 self.template_name = 'home' + '.' + self.template_type
224 else: 222 else:
225 self.template_type = '' 223 self.template = ''
226 self.template_name = 'home'
227 return 224 return
228 elif path[0] == '_file': 225 elif path[0] == '_file':
229 raise SendStaticFile, path[1] 226 raise SendStaticFile, path[1]
230 else: 227 else:
231 self.classname = path[0] 228 self.classname = path[0]
237 m = dre.match(self.classname) 234 m = dre.match(self.classname)
238 if m: 235 if m:
239 self.classname = m.group(1) 236 self.classname = m.group(1)
240 self.nodeid = m.group(2) 237 self.nodeid = m.group(2)
241 # with a designator, we default to item view 238 # with a designator, we default to item view
242 self.template_type = 'item' 239 self.template = 'item'
243 else: 240 else:
244 # with only a class, we default to index view 241 # with only a class, we default to index view
245 self.template_type = 'index' 242 self.template = 'index'
246 243
247 # see if we have a template override 244 # see if we have a template override
248 if self.form.has_key(':template'): 245 if self.form.has_key(':template'):
249 self.template_type = self.form[':template'].value 246 self.template = self.form[':template'].value
250 247
251 248
252 # see if we were passed in a message 249 # see if we were passed in a message
253 if self.form.has_key(':ok_message'): 250 if self.form.has_key(':ok_message'):
254 self.ok_message.append(self.form[':ok_message'].value) 251 self.ok_message.append(self.form[':ok_message'].value)
255 if self.form.has_key(':error_message'): 252 if self.form.has_key(':error_message'):
256 self.error_message.append(self.form[':error_message'].value) 253 self.error_message.append(self.form[':error_message'].value)
257
258 # we have the template name now
259 self.template_name = self.classname + '.' + self.template_type
260 254
261 def serve_file(self, designator, dre=re.compile(r'([^\d]+)(\d+)')): 255 def serve_file(self, designator, dre=re.compile(r'([^\d]+)(\d+)')):
262 ''' Serve the file from the content property of the designated item. 256 ''' Serve the file from the content property of the designated item.
263 ''' 257 '''
264 m = dre.match(str(designator)) 258 m = dre.match(str(designator))
277 # we just want to serve up the file named 271 # we just want to serve up the file named
278 mt = mimetypes.guess_type(str(file))[0] 272 mt = mimetypes.guess_type(str(file))[0]
279 self.header({'Content-Type': mt}) 273 self.header({'Content-Type': mt})
280 self.write(open(os.path.join(self.instance.TEMPLATES, file)).read()) 274 self.write(open(os.path.join(self.instance.TEMPLATES, file)).read())
281 275
282 def template(self, name, **kwargs): 276 def renderTemplate(self, name, extension, **kwargs):
283 ''' Return a PageTemplate for the named page 277 ''' Return a PageTemplate for the named page
284 ''' 278 '''
285 pt = getTemplate(self.instance.TEMPLATES, name) 279 pt = getTemplate(self.instance.TEMPLATES, name, extension)
286 # XXX handle PT rendering errors here more nicely 280 # XXX handle PT rendering errors here more nicely
287 try: 281 try:
288 # let the template render figure stuff out 282 # let the template render figure stuff out
289 return pt.render(self, None, None, **kwargs) 283 return pt.render(self, None, None, **kwargs)
290 except PageTemplate.PTRuntimeError, message: 284 except PageTemplate.PTRuntimeError, message:
295 return cgitb.html() 289 return cgitb.html()
296 290
297 def content(self): 291 def content(self):
298 ''' Callback used by the page template to render the content of 292 ''' Callback used by the page template to render the content of
299 the page. 293 the page.
294
295 If we don't have a specific class to display, that is none was
296 determined in determine_context(), then we display a "home"
297 template.
300 ''' 298 '''
301 # now render the page content using the template we determined in 299 # now render the page content using the template we determined in
302 # determine_context 300 # determine_context
303 return self.template(self.template_name) 301 if self.classname is None:
302 name = 'home'
303 else:
304 name = self.classname
305 return self.renderTemplate(self.classname, self.template)
304 306
305 # these are the actions that are available 307 # these are the actions that are available
306 actions = { 308 actions = {
307 'edit': 'editItemAction', 309 'edit': 'editItemAction',
310 'editCSV': 'editCSVAction',
308 'new': 'newItemAction', 311 'new': 'newItemAction',
309 'register': 'registerAction', 312 'register': 'registerAction',
310 'login': 'login_action', 313 'login': 'login_action',
311 'logout': 'logout_action', 314 'logout': 'logout_action',
312 'search': 'searchAction', 315 'search': 'searchAction',
629 632
630 if not self.newItemPermission(props): 633 if not self.newItemPermission(props):
631 self.error_message.append( 634 self.error_message.append(
632 _('You do not have permission to create %s' %self.classname)) 635 _('You do not have permission to create %s' %self.classname))
633 636
634 # XXX 637 # create a little extra message for anticipated :link / :multilink
635 # cl = self.db.classes[cn] 638 if self.form.has_key(':multilink'):
636 # if self.form.has_key(':multilink'): 639 link = self.form[':multilink'].value
637 # link = self.form[':multilink'].value 640 elif self.form.has_key(':link'):
638 # designator, linkprop = link.split(':') 641 link = self.form[':multilink'].value
639 # xtra = ' for <a href="%s">%s</a>' % (designator, designator) 642 else:
640 # else: 643 link = None
641 # xtra = '' 644 xtra = ''
645 if link:
646 designator, linkprop = link.split(':')
647 xtra = ' for <a href="%s">%s</a>'%(designator, designator)
642 648
643 try: 649 try:
644 # do the create 650 # do the create
645 nid = self._createnode(props) 651 nid = self._createnode(props)
646 652
652 658
653 # render the newly created item 659 # render the newly created item
654 self.nodeid = nid 660 self.nodeid = nid
655 661
656 # and some nice feedback for the user 662 # and some nice feedback for the user
657 message = _('%(classname)s created ok')%self.__dict__ 663 message = _('%(classname)s created ok')%self.__dict__ + xtra
658 except (ValueError, KeyError), message: 664 except (ValueError, KeyError), message:
659 self.error_message.append(_('Error: ') + str(message)) 665 self.error_message.append(_('Error: ') + str(message))
660 return 666 return
661 except: 667 except:
662 # oops 668 # oops
684 return 1 690 return 1
685 if has('Edit', self.userid, self.classname): 691 if has('Edit', self.userid, self.classname):
686 return 1 692 return 1
687 return 0 693 return 0
688 694
689 def genericEditAction(self): 695 def editCSVAction(self):
690 ''' Performs an edit of all of a class' items in one go. 696 ''' Performs an edit of all of a class' items in one go.
691 697
692 The "rows" CGI var defines the CSV-formatted entries for the 698 The "rows" CGI var defines the CSV-formatted entries for the
693 class. New nodes are identified by the ID 'X' (or any other 699 class. New nodes are identified by the ID 'X' (or any other
694 non-existent ID) and removed lines are retired. 700 non-existent ID) and removed lines are retired.
695 ''' 701 '''
696 # generic edit is per-class only 702 # this is per-class only
697 if not self.genericEditPermission(): 703 if not self.editCSVPermission():
698 self.error_message.append( 704 self.error_message.append(
699 _('You do not have permission to edit %s' %self.classname)) 705 _('You do not have permission to edit %s' %self.classname))
700 706
701 # get the CSV module 707 # get the CSV module
702 try: 708 try:
707 'Get it from: <a href="http://www.object-craft.com.au/projects/csv/">http://www.object-craft.com.au/projects/csv/')) 713 'Get it from: <a href="http://www.object-craft.com.au/projects/csv/">http://www.object-craft.com.au/projects/csv/'))
708 return 714 return
709 715
710 cl = self.db.classes[self.classname] 716 cl = self.db.classes[self.classname]
711 idlessprops = cl.getprops(protected=0).keys() 717 idlessprops = cl.getprops(protected=0).keys()
718 idlessprops.sort()
712 props = ['id'] + idlessprops 719 props = ['id'] + idlessprops
713 720
714 # do the edit 721 # do the edit
715 rows = self.form['rows'].value.splitlines() 722 rows = self.form['rows'].value.splitlines()
716 p = csv.parser() 723 p = csv.parser()
717 found = {} 724 found = {}
718 line = 0 725 line = 0
719 for row in rows: 726 for row in rows[1:]:
720 line += 1 727 line += 1
721 values = p.parse(row) 728 values = p.parse(row)
722 # not a complete row, keep going 729 # not a complete row, keep going
723 if not values: continue 730 if not values: continue
724 731
732 # skip property names header
733 if values == props:
734 continue
735
725 # extract the nodeid 736 # extract the nodeid
726 nodeid, values = values[0], values[1:] 737 nodeid, values = values[0], values[1:]
727 found[nodeid] = 1 738 found[nodeid] = 1
728 739
729 # confirm correct weight 740 # confirm correct weight
730 if len(idlessprops) != len(values): 741 if len(idlessprops) != len(values):
731 message=(_('Not enough values on line %(line)s'%{'line':line})) 742 self.error_message.append(
743 _('Not enough values on line %(line)s')%{'line':line})
732 return 744 return
733 745
734 # extract the new values 746 # extract the new values
735 d = {} 747 d = {}
736 for name, value in zip(idlessprops, values): 748 for name, value in zip(idlessprops, values):
753 # retire the removed entries 765 # retire the removed entries
754 for nodeid in cl.list(): 766 for nodeid in cl.list():
755 if not found.has_key(nodeid): 767 if not found.has_key(nodeid):
756 cl.retire(nodeid) 768 cl.retire(nodeid)
757 769
758 message = _('items edited OK') 770 # all OK
759 771 self.db.commit()
760 # redirect to the class' edit page 772
761 raise Redirect, '%s/%s?:ok_message=%s'%(self.base, self.classname, 773 self.ok_message.append(_('Items edited OK'))
762 urllib.quote(message)) 774
763 775 def editCSVPermission(self):
764 def genericEditPermission(self):
765 ''' Determine whether the user has permission to edit this class. 776 ''' Determine whether the user has permission to edit this class.
766 777
767 Base behaviour is to check the user can edit this class. 778 Base behaviour is to check the user can edit this class.
768 ''' 779 '''
769 if not self.db.security.hasPermission('Edit', self.userid, 780 if not self.db.security.hasPermission('Edit', self.userid,

Roundup Issue Tracker: http://roundup-tracker.org/