Example #1
0
	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
Example #2
0
	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()
Example #3
0
	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
Example #4
0
	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
Example #5
0
	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
Example #6
0
	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
Example #7
0
	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
Example #8
0
	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
Example #9
0
	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
Example #10
0
	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
Example #11
0
	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
Example #12
0
    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"])
Example #13
0
				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):