"""A set of edit pages for a Subversion website setup""" try: from mod_python import apache, util except ImportError: from dummy import apache, util import py from base import handler_decorator class editbase(handler_decorator): """base class for edit pages""" def __init__(self, template, condition=lambda r: True, replacebody=True): self.template = template self.condition = condition self.replacebody = replacebody def handler(self, orghandler, request): fpath = py.path.svnwc(request.filename) # bail out before directory indexes are handled if (not self.base_condition(request, fpath) or not self.condition(request)): return orghandler(request) if not self.replacebody: data = orghandler(request) if not isinstance(data, dict): return data else: data = { 'head': [], 'body': [], } tree = self.render_template(request, fpath) request.content_type = 'text/html; charset=UTF-8' data['head'] += list(tree.find('head'))[0] data['body'] += list(tree.find('body'))[0] return data def render_template(self, request, fpath): """render the template this should generally be left alone """ from templess import templess tpath = py.path.local(self.template) t = templess.template(tpath.open()) env = self.get_env(request, fpath) return t.render(env) def get_env(self, request, fpath): """create the environment for the template this should return a dict, the keys are determined by the template """ return {} def base_condition(self, request, fpath): """see whether we should handle requests should return True or False depending on whether we're interested to handle this request usually this checks the file extension, whether the file the URL points to is a dir, etc. """ return True # expects a dict with head and body keys, appends to or replaces body with # svn property form, handles the properties class svnpropertyeditor(editbase): """set svn properties on files in a form properties is a list of tuples (name, title, type, required, validator) where 'name' is the name of the SVN property, 'title' the title displayed for the field and 'type' either 'text', 'textarea' or 'password' """ # if the POST body contains this field, the form is validated; either give # your submit button this name, or place a hidden field with this name in # your form submitfieldname = 'svnpropertyeditor' def __init__(self, properties, template, condition=lambda r: True, replacebody=False): super(svnpropertyeditor, self).__init__(template, condition, replacebody) self.properties = properties self.propmapping = dict([(x[0], x[1:]) for x in properties]) def get_env(self, request, fpath): errors = self.handle_form(request, fpath) env = { 'formaction': request.uri, 'submitfieldname': self.submitfieldname, 'errors': ['%s: %s' % t for t in errors.items()], 'haveerrors': not not errors, 'noerrors': not errors, 'properties': [{ 'name': name, 'title': title, 'type': type, 'required': required, 'istextarea': type == 'textarea', 'isinput': type == 'input', 'value': (request.form.has_key(name) and request.form[name].value or fpath.propget(name)), 'validated': not errors.has_key(name), 'invalid': errors.has_key(name), 'errormessage': errors.get(name, ''), } for (name, title, type, required, v) in self.properties], } return env def handle_form(self, request, fpath): """returns a dict {: } if a field is not in the dict, it has validated successfully """ if (request.method != 'POST' or not request.form.has_key(self.submitfieldname)): return {} validated = dict([(key, False) for key in self.propmapping]) for key in request.form.keys(): if self.propmapping.has_key(key): value = request.form[key].value title, type, required, validator = self.propmapping[key] if (not value and not required) or validator(value): fpath.propset(key, request.form[key].value) validated[key] = True return validated class kupueditor(editbase): """kupu editor page""" def get_env(self, request, fpath): uri = request.uri if request.path_info: uri = uri[:-len(request.path_info)] env = { 'data': fpath.read(), 'formaction': uri, } return env def base_condition(self, request, fpath): return fpath.ext in ['.htm', '.html'] class managedir(editbase): """manage directories allows viewing a dir listing, adding, removing and deleting items, and makes sure that stuff is added to and removed from SVN and such """ def __init__(self, siteroot, template, createtemplatedir, condition=lambda r: (r.form.has_key('edit') and r.form['edit'].value == 'true'), createeditlink=lambda p: '%s?edit=true' % (p,), replacebody=True): super(managedir, self).__init__(template, condition, replacebody) self.siteroot = siteroot self.createtemplatedir = createtemplatedir self.createeditlink = createeditlink def get_env(self, request, fpath): errors = self.handle_form(request, fpath) diruri = request.uri if request.path_info: diruri = diruri[:-len(request.path_info)] env = { 'notroot': diruri[:-1] != self.siteroot, 'dirname': fpath.basename, 'errors': ['%s: %s' % t for t in errors.items()], 'haveerrors': not not errors, 'items': [], } fpaths = [fp for fp in fpath if not fp.basename.startswith('.')] fpaths.sort(key=lambda x: x.basename) svnst = fpath.status() unchanged = svnst.unchanged for fp in fpaths: env['items'].append({ 'fname': fp.basename, 'isdir': fp.check(dir=True), 'isfile': fp.check(file=True), 'uri': '%s%s' % (diruri, fp.basename), 'edituri': self.createeditlink('%s%s' % (diruri, fp.basename)), 'ismodified': fp not in unchanged, 'diff': fp not in unchanged and fp.diff() or '', 'size': fp.size(), }) return env def base_condition(self, request, fpath): return fpath.check(dir=True) def handle_form(self, request, fpath): """handle form actions return a dict {id: errormessage} for each error encountered """ errors = {} form = request.form if form.has_key('delete'): self.handle_delete(form, fpath, errors) if form.has_key('rename'): self.handle_rename(form, fpath, errors) if form.has_key('add'): self.handle_add(form, fpath, errors) if form.has_key('commit'): self.handle_commit(form, fpath, errors) return errors def handle_delete(self, form, fpath, errors): todelete = form['ids'].value if not isinstance(todelete, list): todelete = [todelete] for item in todelete: ipath = fpath / item # remove item try: ipath.remove() except StandardError, e: errors[item] = 'could not delete: %r' % (e,) def handle_rename(self, form, fpath, errors): if not form.has_key('ids'): errors['rename'] = 'please select an item to rename' return torename = form['ids'].value # XXX only one item at a time for now... if not isinstance(torename, list): renameto = form['renameto'].value if instance(renameto, list): if renameto: rpath = fpath / torename try: rpath.rename(renameto) except StandardError, e: errors[torename] = e else: errors['rename'] = 'please enter a new name' else: errors['rename'] = 'only one name allowed' else: errors['rename'] = 'only one item allowed' def handle_add(self, form, fpath, errors): namefield = form.get('name', None) if namefield is None or not namefield.value: errors['add'] = 'no name supplied' return name = namefield.value typefield = form.get('type', None) if typefield is None or not typefield.value: errors['add'] = 'no type supplied' return type = typefield.value newpath = fpath / name if newpath.check(): errors['add'] = 'name already in use' return if type == 'dir': newpath.mkdir() else: upload = form.get('upload', None) if upload is not None and upload.filename: self._copy_file(upload.file, newpath.open('w')) else: templatepath = py.path.local(self.createtemplatedir) / type if templatepath.check(): newpath.write(templatepath.read()) else: newpath.write('') # add to svn newpath.add() def _copy_file(self, origin, target): bufsize = 1024 while 1: chunk = origin.read(bufsize) if not chunk: break target.write(chunk) def handle_commit(self, form, fpath, errors): if not form.has_key('ids'): errors['commit'] = 'please select which items to commit' return if not form.has_key('message') or not form['message'].value: errors['commit'] = 'please enter a commit message' return ids = form['ids'].value if not isinstance(ids, list): ids = [ids] for id in ids: cipath = fpath / id try: cipath.commit(form['message'].value) except StandardError, e: errors[id] = e def handle_rollback(self, form, fpath, errors): if not form.has_key('ids'): errors['rollback'] = 'please select which items to roll back' return ids = form['ids'].value if not isinstance(ids, list): ids = [ids] for id in ids: cipath = fpath / id try: cipath.revert() except StandardError, e: errors[id] = e class svnhistory(editbase): def get_env(self, request, fpath): log = fpath.log() time = py.std.time env = { 'log': [ {'date': time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(l.date)), 'rev': l.rev, 'author': getattr(l, 'author', 'none'), 'message': l.msg, } for l in log ], } return env class editnav(editbase): def __init__(self, template, links, condition=lambda r: True): super(editnav, self).__init__(template, condition, False) self.links = links def get_env(self, request, fpath): return { 'links': [ {'url': x[0], 'title': x[1], } for x in self.links ], }