def move_category_to_new_parent(self, category_id, new_parent_id, all_categories = None): """ Move a category (and its child categories) to new context, possibly setting the parent category to null. :param int category: category moved :param int new_parent_id: new parent id :param all_categories: equal to self.get_all_categories() """ if not all_categories: all_categories = self.get_all_categories() # all_categories comes from database # Validate category_id category_id = safe_int(category_id) if not all_categories.has_key(category_id): raise Exception("No repositioned category found.") category = all_categories[category_id] parent_category = None # Validate new_parent_id new_parent_id = safe_int(new_parent_id) if not all_categories.has_key(new_parent_id): raise Exception("No new parent category found.") parent_category = all_categories[new_parent_id] must_update_context = False if category.parent == new_parent_id and parent_category.context == category.context: raise Exception("Category's context and parent are already as required.") # Prevent making eternal loops. is_sub_category = self._is_sub_category_or_self(new_parent_id, category_id, all_categories) if is_sub_category: raise Exception("Cannot move category under its sub category.") change_context_query = '' if parent_category.context != category.context: must_update_context = True change_context_query = self._change_context_query(category_id, all_categories) try: with admin_transaction() as cursor: if must_update_context: cursor.execute(change_context_query, parent_category.context) cursor.execute("UPDATE `categories` " " SET `parent_id` = %s " " WHERE `category_id` = %s ", (new_parent_id, category_id)) except Exception as e: conf.log.exception("Failed to change parent category of %s to be %d: %s", category.name, new_parent_id, e) raise Exception("Error when updating parent.") finally: cache = CategoryCache.instance() cache.clearAllCategories()
def move_category_to_root_of_context(self, category_id, new_context_id, all_categories = None): """ Move a category (and its child categories) to new context (if not already there) and set the parent category to null. :param int category: category moved :param int new_context_id: new context :param all_categories: equal to self.get_all_categories() """ if not all_categories: all_categories = self.get_all_categories() # all_categories comes from database # Validate new_context_id new_context_id = safe_int(new_context_id) context = self.get_context_by_id(new_context_id) if not context: raise Exception("Context was invalid.") # Validate category_id category_id = safe_int(category_id) if not all_categories.has_key(category_id): raise Exception("Category not found.") category = all_categories[category_id] must_change_context = True if category.context == new_context_id: if category.parent is None: raise Exception("Category is already a root category and has the required context.") else: must_change_context = False change_context_query = self._change_context_query(category_id, all_categories) try: with admin_transaction() as cursor: if must_change_context: cursor.execute(change_context_query, new_context_id) cursor.execute("UPDATE `categories` " " SET `parent_id` = NULL " " WHERE `category_id` = %s ", category_id) except: conf.log.exception("Failed to move category %s into context %d", category.name, new_context_id) raise Exception("Error when doing database transaction.") finally: cache = CategoryCache.instance() cache.clearAllCategories()
def get_all_categories_in_context(self, context_id): """ :returns: A flat dictionary of categories in context with category id as index:: int category_id => Category category .. Note:: All child categories are populated so that it's easy to start walking from any category through children. Also, the root categories (not child categories) are ordered by category_name. """ # Try from cache cache = CategoryCache.instance() categories = cache.get_categories_in_context(context_id) if categories: return categories # Try from db query = "SELECT * FROM categories WHERE context_id = %s ORDER BY category_name" # First pass, Read all categories to hash id => categories categories = {} with admin_query() as cursor: try: cursor.execute(query, context_id) for row in cursor: cat = Category.from_sql_row(row) categories[cat.category_id] = cat except: conf.log.exception("Failed on building category tree") # Second pass, create hierarchy for id, category in categories.items(): # Add category as a parent category's child category if category.parent: try: categories[category.parent].add_child(category) except: conf.log.warning("Inconsistent data. No parent category %d found" % category.parent) if categories: cache.set_categories_in_context(categories, context_id) return categories
def remove_category(self, category_id): """ Remove a category. Raises Exception on error. """ category_id = safe_int(category_id) if not category_id: raise Exception("Invalid category provided.") query = "DELETE FROM categories WHERE category_id = %s" with admin_transaction() as cursor: try: cursor.execute(query, category_id) except: conf.log.exception("Failed to delete category %d" % category_id) raise Exception("Error when removing category.") finally: cache = CategoryCache.instance() cache.clearAllCategories()
def get_all_project_categories(self, project_key, ordered = False): """ Returns a list of all project categories of the project. """ cc = CategoryCache.instance() project_key = safe_int(project_key) categories = cc.getProjectCategories(project_key) if categories: return categories order_by = '' if ordered: order_by = 'ORDER BY categories.context_id, categories.category_name' query = """ SELECT categories.* FROM categories INNER JOIN project_categories ON project_categories.category_key = categories.category_id WHERE project_categories.project_key = %s {order_by}""".format(order_by=order_by) categories = [] with admin_query() as cursor: try: cursor.execute(query, (project_key,)) for row in cursor: cat = Category.from_sql_row(row) categories.append(cat) if len(categories) > 0: cc.setProjectCategories(project_key, categories) except: conf.log.exception("Exception. Query failed when searching project categories. Query('%s')." % str(query)) return categories
def edit_category(self, category_id, category_name, category_description): """ Edits a category name and description. Raises Exception on error. """ category_id = safe_int(category_id) if not category_id: raise Exception("Invalid category provided.") existing_category = self.get_category_by_name(category_name) if existing_category and existing_category.category_id != category_id: raise Exception("A category with the same name already exists.") try: with admin_transaction() as cursor: cursor.execute("UPDATE `categories`" " SET `category_name` = %s, `description` = %s " " WHERE `category_id` = %s", (category_name, category_description, category_id)) except: conf.log.exception("Failed to edit category %d" % category_id) raise Exception("Error when editing category.") finally: cache = CategoryCache.instance() cache.clearAllCategories()
def add_category(self, name, description, context, parent): """ Add a new category. Raises Exception on error. """ parent = safe_int(parent) name = str(name) context = safe_int(context) query = ("INSERT INTO `categories` (`category_name`, `description`, `parent_id`, `context_id`)" " VALUES(%s, %s, %s, %s);") existing_category = self.get_category_by_name(name) if existing_category: raise Exception("A category with the same name already exists.") try: with admin_transaction() as cursor: cursor.execute(query, (name, description, parent, context)) except: conf.log.exception("Error when adding category.") raise Exception("Error when adding category.") finally: cache = CategoryCache.instance() cache.clearAllCategories()
def merge_category_to_category(self, category_id, target_category_id, all_categories = None): """ Updates projects pointing to category to use target_category, and removes category. Updates also contexts of category and its sub categories. Raises exception on error. """ if not all_categories: all_categories = self.get_all_categories() # all_categories comes from database # Validate category_id category_id = safe_int(category_id) if not all_categories.has_key(category_id): raise Exception("Invalid merged category") category = all_categories[category_id] # Validate target_category_id target_category_id = safe_int(target_category_id) if not all_categories.has_key(target_category_id): raise Exception("Invalid target category") target_category = all_categories[target_category_id] # If the categories are the same, already fixed. if category_id == target_category_id: raise Exception("Two same categories given.") # Prevent making eternal loops by checking that # target category is not sub category of merged category. is_sub_category = self._is_sub_category_or_self(target_category_id, category_id, all_categories) if is_sub_category: raise Exception("Cannot merge category to sub category.") must_update_context = False change_context_query = '' # Check whether to update contexts if target_category.context != category.context: must_update_context = True change_context_query = self._change_context_query(category_id, all_categories) get_project_ids_with_both_categories = ("" " SELECT `project_key` FROM `project_categories` pk1 " " WHERE pk1.`category_key` = %s " " AND EXISTS (SELECT pk2.`project_key` FROM `project_categories` pk2 " " WHERE pk1.`project_key` = pk2.`project_key` AND pk2.`category_key` = %s) ") update_project_categories = (" UPDATE `project_categories` " " SET `category_key` = %(target_category_id)s " " WHERE `category_key` = %(category_id)s " " AND `project_key` NOT IN (%(project_ids)s)") update_sub_category_parents = ("UPDATE `categories` " " SET `parent_id` = %(target_category_id)s " " WHERE `parent_id` = %(category_id)s ") try: with admin_transaction() as cursor: # Get projects which have both target and merged categories cursor.execute(get_project_ids_with_both_categories, (category_id, target_category_id)) ids = [] for row in cursor: ids.append(str(row[0])) # Update existing projects to use target_category cursor.execute(update_project_categories, {'target_category_id' : target_category_id, 'category_id' : category_id, 'project_ids' : (', '.join(ids))}) # Update the parent of child categories cursor.execute(update_sub_category_parents, {'target_category_id' : target_category_id, 'category_id' : category_id}) # Update the context of sub categories if must_update_context: cursor.execute(change_context_query, target_category.context) cursor.execute("DELETE FROM `categories` WHERE `category_id` = %s ", category_id) except Exception as e: conf.log.exception("Failed to merge category '%s' to '%s'" % (str(category.name), str(target_category.name))) raise Exception("Error when merging categories.") finally: cache = CategoryCache.instance() cache.clearAllCategories()