Mercurial > p > roundup > code
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, |
