def validate(self, req, frm, storable): """ The validation function for forms generated from this itemdef. @param req: The current request @type req: L{modu.web.app.Request} @param frm: The current form @type frm: L{modu.util.form.FormNode} @param storable: The Storable instance associated with this form. @type storable: list(L{modu.persist.storable.Storable}) @return: True if valid @rtype: bool """ form_data = req.data[frm.name] if('referer' in frm): referer = frm['referer'].value else: referer = None if(frm.submit_button.value == form_data.get('cancel', None).value): if(referer): app.redirect(referer) else: app.redirect(req.get_path(req.prepath, 'listing', storable.get_table())) elif(frm.submit_button.value == form_data.get('delete', None).value): if(self.delete(req, frm, storable)): if(referer): app.redirect(referer) elif(self.config.get('hidden', False)): app.redirect(req.get_path(req.prepath)) else: app.redirect(req.get_path(req.prepath, 'listing', storable.get_table())) else: return False if(frm.submit_button.value != form_data.get('save', None).value): validator = frm.submit_button.attr('validator', None) if(callable(validator)): return validator(req, frm, storable) req.messages.report('error', "A custom submit button was used, but no validator function found.") return False else: # call validate hook on each field, return false if they do for field in frm: if(field in self and 'validator' in self[field]): validator = self[field]['validator'] if(validator): if not(validator(req, frm, storable)): return False else: if not(frm[field].validate(req, frm)): return False return True
def _destroy(self, storable): """ Internal function that is responsible for doing the actual destruction. """ factory = storable.get_factory() if(factory.uses_guids()): primary_key = factory.get_primary_key() delete_query = sql.build_delete(storable.get_table(), {primary_key:storable.get_id()}) else: delete_query = sql.build_delete(storable.get_table(), storable.get_data()) self.pool.runOperation(delete_query) storable.reset_id()
def fetch_id(self, storable): """ Fetch an ID for this object, if possible. "Predict" the id for this storable. If GUIDs are being used, this method will fetch a new GUID, set it, and return that ID immediately (assuming that this object will ultimately be saved with that ID). If GUIDs are not being used, this method will return 0 if this is an unsaved object. It's not possible to use "predictive" IDs in that case. @see: L{IFactory.fetch_id()} @param storable: the object whose ID you wish to fetch @type storable: L{storable.Storable} @returns: the item's id, or 0 @rtype: int @raises LookupError: if no factory has been registered for this Storable's table """ id = storable.get_id() if(id == 0): table = storable.get_table() if not(self.has_factory(table)): raise LookupError('There is no factory registered for the table `%s`' % table) factory = self.get_factory(table) use_locks = (not bool(self.req)) or self.req.app.config.get('use_db_locks', True) new_id = factory.get_id(use_locks=use_locks) storable.set_id(new_id, set_old=False) return new_id return id
def get_form(self, req, storable): """ Return a FormNode that represents this item in a detail view. If a user object is passed along, the resulting form will only contain fields the provided user is allowed to see. @param req: The current request @type req: L{modu.web.app.Request} @param storable: The Storable instance to display. @type storable: L{modu.persist.storable.Storable} @return: Parent form object @rtype: L{modu.util.form.FormNode} """ frm = form.FormNode('%s-form' % storable.get_table()) read_only = self.config.get('read_only', not self.allows(req.user, 'edit')) if(self.allows(req.user, 'view')): # if user can access detail view for name, field in self.items(): if(name.startswith('_')): continue if(not field.get('detail', True)): continue if not(field.allows(req.user, 'view')): # if user cannot access field continue if not(field.allows(req.user, 'edit')): field['read_only'] = True if(not field.get('acl', []) and not self.allows(req.user, 'edit')): field['read_only'] = True frm[name] = field.get_form_element(req, 'detail', storable) if not(read_only): frm['save'](type='submit', value='save') frm['cancel'](type='submit', value='cancel') if(self.deletable(req)): frm['delete'](type='submit', value='delete', attributes={'onClick':"return confirm('Are you sure you want to delete this record?');"}) def _validate(req, form): return self.validate(req, form, storable) def _submit(req, form): return self.submit(req, form, storable) frm.validate = _validate frm.submit = _submit return frm
def delete(self, req, form, storable): """ The delete function for forms generated from this itemdef. @param req: The current request @type req: L{modu.web.app.Request} @param frm: The current form @type frm: L{modu.util.form.FormNode} @param storable: The Storable instance to delete. @type storable: list(L{modu.persist.storable.Storable}) @return: True if deleted @rtype: bool """ if not(self.deletable(req)): req.messages.report('error', "You are not allowed to delete `%s` records." % storable.get_table()) return False if('predelete_callback' in self.config): result = self.config['predelete_callback'](req, form, storable) if(result is False): return False deleted_id = storable.get_id() deleted_table = storable.get_table() storable.get_store().destroy(storable, self.config.get('delete_related_storables', False)) if('postdelete_callback' in self.config): self.config['postdelete_callback'](req, form, storable) req.messages.report('message', "Record #%d in %s was deleted." % (deleted_id, deleted_table)) if('referer' in form): referer = form['referer'].value else: referer = None app.redirect(referer) return True
def get_form_element(self, req, style, storable): """ Get a FormNode element that represents this field. The parent itemdef class will call this function, which will call the get_element() method, and set a few default values. @param req: The current request @type req: L{modu.web.app.Request} @param style: Generate for 'listing', 'search', or 'detail' views. @type style: str @param storable: The Storable instance to fill form data with. @type storable: L{modu.persist.storable.Storable} subclass @return: form element @rtype: L{modu.util.form.FormNode} """ frm = self.get_element(req, style, storable) classes = [self.__class__] while(classes): for cls in classes: if not(hasattr(cls, 'inherited_attributes')): continue for name in cls.inherited_attributes: if(name in self and name not in frm.attributes): frm.attributes[name] = self[name] classes = cls.__bases__ for name, value in self.get('attributes', {}).iteritems(): frm.attributes[name] = value # Since the templates take care of the wrapper, all # elements are defined as basic elements unless set # explicitly by the field definition. if(style != 'search' and 'basic_element' not in frm.attributes): frm(basic_element=True) if(style == 'listing' and self.get('link', False)): href = req.get_path(req.prepath, 'detail', storable.get_table(), storable.get_id()) frm(prefix=tags.a(href=href, __no_close=True), suffix='</a>') if(self.get('required', False)): frm(required=True) if(callable(self.get('form_alter', None))): self['form_alter'](req, style, frm, storable, self) return frm
def _save(self, storable, factory): """ Internal function that is responsible for doing the actual saving. """ if not(storable.is_dirty()): return False table = storable.get_table() data = storable.get_data() locks_allowed = (not bool(self.req)) or self.req.app.config.get('use_db_locks', True) use_locks = False primary_key = factory.get_primary_key() if(storable.is_new()): if(factory.uses_guids()): data[primary_key] = self.fetch_id(storable) elif(locks_allowed): use_locks = True self.pool.runOperation('LOCK TABLES `%s` WRITE' % table) else: raise RuntimeError("Broken program logic is trying to lock an unlockable database.") query = sql.build_insert(table, data) else: query = sql.build_update(table, data, {primary_key:storable.get_id()}) self.log(query) try: self.pool.runOperation(query) if not(factory.uses_guids()): if not(storable.get_id()): rows = self.pool.runQuery('SELECT MAX(%s) AS `id` FROM `%s`' % (primary_key, table)) new_id = rows[0]['id'] storable.set_id(new_id) finally: if(use_locks): self.pool.runOperation('UNLOCK TABLES') storable.clean() storable.set_new(False) storable.set_factory(factory) return True
def get_listing(self, req, storables): """ Return a FormNode that represents this item in a list view. This function returns a list of form objects that can be assembled into a list view (one "form" per row). @param req: The current request @type req: L{modu.web.app.Request} @param storable: The Storable instances to display. @type storable: list(L{modu.persist.storable.Storable}) @return: List of parent form objects @rtype: L{modu.util.form.FormNode} """ forms = [] if not(self.allows(req.user, 'list')): # if user cannot see listing return forms for index in range(len(storables)): storable = storables[index] frm = form.FormNode('%s-row' % storable.get_table()) for name, field in self.items(): if(name.startswith('_')): continue if(not field.get('listing', False)): continue if not(field.allows(req.user, 'list')): # if user cannot see listing field continue frm[name] = field.get_form_element(req, 'listing', storable) def _validate(req, form): return self.validate(req, form, storable) def _submit(req, form): self.submit(req, form, storable) frm.validate = _validate frm.submit = _submit forms.append(frm) return forms
def save(self, storable, save_related_storables=True): """ Save the provided Storable. @see: L{IFactory.save()} @param storable: the object you wish to save @type storable: L{storable.Storable} @param save_related_storables: should the items returned by L{Storable.get_related_storables()} be automatically saved? @type save_related_storables: bool @raises LookupError: if no factory has been registered for this Storable's table """ table = storable.get_table() if(table not in self._factories): raise LookupError('There is no factory registered for the table `%s`' % table) factory = self._factories[table] result = self._save(storable, factory) child_list = storable.get_related_storables() id_list = [] while(child_list and save_related_storables): child = child_list.pop() child_id = self.fetch_id(child) child_table = child.get_table() if(child_table not in self._factories): raise LookupError('There is no factory registered for the table `%s`' % child_table) factory = self._factories[child_table] if(child_id in id_list and factory.uses_guids()): #raise AssertionError('Found circular storable reference during save') continue result = result and self._save(child, factory) child_list.extend(child.get_related_storables()) id_list.append(child_id) return result
def update_storable(self, req, form, storable): """ Given the posted data in req, update provided storable with this field's content. @param req: The current request @type req: L{modu.web.app.Request} @param frm: The current form @type frm: L{modu.util.form.FormNode} @param storable: The Storable instance to fill form data with. @type storable: L{modu.persist.storable.Storable} subclass @return: False to abort the save, True to continue @rtype: bool """ form_name = '%s-form' % storable.get_table() if(form_name in req.data): form_data = req.data[form_name] if(self.name in form_data): setattr(storable, self.get_column_name(), form_data[self.name].value) return True
def get_search_form(self, req, storable): """ Return a FormNode that represents this item in a search view. If a user object is passed along, the resulting form will only contain search fields the provided user is allowed to see. @param req: The current request @type req: L{modu.web.app.Request} @param storable: The Storable instance to display. @type storable: L{modu.persist.storable.Storable} @return: Parent form object @rtype: L{modu.util.form.FormNode} """ frm = form.FormNode('%s-search-form' % storable.get_table()) if(self.allows(req.user, 'search')): # if user can access search form for name, field in self.items(): if(name.startswith('_')): continue if not(field.allows(req.user, 'search')): # if user cannot use search field continue if not(field.get('search', False)): continue frm[name] = field.get_form_element(req, 'search', storable) if(len(frm) and not frm.has_submit_buttons()): frm['search'](type='submit', value='search', weight=1000) frm['clear_search'](type='submit', value='clear search', weight=1000) # search should always work...for now frm.validate = lambda r, f: True # submission doesn't really do much, since the editable # resource will handle that frm.submit = lambda r, f: True return frm
def prepare_detail(self, req, itemdef): """ Handle creation and display of the detail page. @param req: the current request @type req: L{modu.web.app.Request} @param itemdef: the itemdef to use to generate the form @type itemdef: L{modu.editable.define.itemdef} """ self.template = itemdef.config.get("detail_template", "admin-detail.html.tmpl") self.set_slot("form", None) if len(req.postpath) > 2: item_id = req.postpath[2] table_name = itemdef.config.get("table", itemdef.name) if item_id == "new": if not (itemdef.creatable(req)): app.raise403("You are not allowed to delete `%s` records." % storable.get_table()) if "model_class" in itemdef.config: selected_item = itemdef.config["model_class"]() else: selected_item = storable.Storable(table_name) # Populate the new item if necessary query_data = form.parse_query_string(req) if "__init__" in query_data: for k, v in query_data["__init__"].items(): setattr(selected_item, k, v.value) # we can be sure the factory is there, because configure_store # already took care of it during prepare_content factory = req.store.get_factory(table_name) selected_item.set_factory(factory) else: try: selected_item = req.store.load_one(table_name, {"id": int(item_id)}) if selected_item is None: app.raise404("Couldn't load %s record #%s" % (table_name, item_id)) except ValueError: app.raise404("There is no detail view at the path: %s" % req["REQUEST_URI"]) frm = itemdef.get_form(req, selected_item) if "theme" in itemdef.config: frm.theme = itemdef.config["theme"] frm["referer"](type="hidden", value=req.get("HTTP_REFERER", "")) if frm.execute(req): # we regenerate the form because some fields don't know their # value until after the form is saved (e.g., postwrite fields) new_frm = itemdef.get_form(req, selected_item) # this is technically only necessary if errors are reported # on form items but they do not prevent completion (e.g., return False) new_frm.errors = frm.errors frm = new_frm else: # If we haven't submitted the form, errors should definitely be empty def error_formatter(req, field, err): return tags.a(href="#form-item-%s" % field)[err] frm.escalate_errors(req, formatter=error_formatter) template_variable_callback = itemdef.config.get("template_variable_callback") if callable(template_variable_callback): result = template_variable_callback(req, frm, selected_item) if isinstance(result, dict): for key, value in result.items(): self.set_slot(key, value) self.set_slot("form", frm) self.set_slot("theme", frm.get_theme(req)) self.set_slot("selected_item", selected_item) if "title_column" in itemdef.config and item_id != "new": item_name = "'%s'" % getattr(selected_item, itemdef.config["title_column"]) else: item_name = "#" + str(selected_item.get_id()) default_title = "Details for %s %s" % (itemdef.name.title(), item_name) custom_title = itemdef.config.get("detail_title", default_title) self.set_slot("title", tags.encode_htmlentities(custom_title)) else: app.raise404("There is no detail view at the path: %s" % req["REQUEST_URI"])
req.messages.report('error', "An exception occurred during postwrite: %s" % e) reason = failure.Failure() reason.printTraceback(req['wsgi.errors']) postwrite_succeeded = False # call postwrite_callback if('postwrite_callback' in self.config): postwrite_callback = self.config['postwrite_callback'] if not(isinstance(postwrite_callback, (list, tuple))): postwrite_callback = [postwrite_callback] for callback in postwrite_callback: if not(callback(req, form, storable)): postwrite_succeeded = False if(postwrite_succeeded): req.messages.report('message', 'Your changes to %s record #%s have been saved.' % (storable.get_table(), storable.get_id())) if('referer' in form): referer = form['referer'].value else: referer = None if(referer): app.redirect(referer) else: req.messages.report('error', 'There was an error in the postwrite process, but primary record data was saved.') return postwrite_succeeded def deletable(self, req): allow_delete = True if('delete_acl' in self.config):