def __init__(self): self.logger = get_logger(self.__class__.__module__) self.version_info = None self.user_service = UserService() self.flow_service = FlowService() analyze_category = self.flow_service.get_launchable_non_viewers() self.analyze_category_link = '/flow/step/' + str(analyze_category.id) self.analyze_adapters = None self.connectivity_tab_link = '/flow/step_connectivity' view_category = self.flow_service.get_visualisers_category() conn_id = self.flow_service.get_algorithm_by_module_and_class( CONNECTIVITY_MODULE, CONNECTIVITY_CLASS)[1].id connectivity_link = self.get_url_adapter(view_category.id, conn_id) local_connectivity_link = '/spatial/localconnectivity/step_1/1' connectivity_submenu = [ dict(title="Large Scale Connectivity", subsection="connectivity", description= "View Connectivity Regions. Perform Connectivity lesions", link=connectivity_link), dict(title="Local Connectivity", subsection="local", link=local_connectivity_link, description= "Create or view existent Local Connectivity entities.") ] self.connectivity_submenu = connectivity_submenu
def setUp(self): """ Reset the database before each test . """ self.clean_database() self.user_service = UserService() self.user_service.create_user(username=cfg.ADMINISTRATOR_NAME, password=cfg.ADMINISTRATOR_PASSWORD, email=cfg.ADMINISTRATOR_EMAIL, role=model.ROLE_ADMINISTRATOR) available_users = dao.get_all_users() if len(available_users) != 1: self.fail("Something went wrong with database initialization!")
def _to_python(self, value, state): """ Fancy validate for Unique user-name """ if not UserService().is_username_valid(value): raise formencode.Invalid( 'Please choose another user-name, this one is already in use!', value, state) return value
def initialize(introspected_modules, load_xml_events=True): """ Initialize when Application is starting. Check for new algorithms or new DataTypes. """ SettingsService().check_db_url(cfg.DB_URL) ## Initialize DB is_db_empty = initialize_startup() ## Create Projects storage root in case it does not exist. initialize_storage() ## Populate DB algorithms, by introspection event_folders = [] start_introspection_time = datetime.datetime.now() for module in introspected_modules: introspector = Introspector(module) # Introspection is always done, even if DB was not empty. introspector.introspect(True) event_path = introspector.get_events_path() if event_path: event_folders.append(event_path) # Now remove any unverified Algo-Groups, categories or Portlets invalid_stored_entities = dao.get_non_validated_entities( start_introspection_time) for entity in invalid_stored_entities: dao.remove_entity(entity.__class__, entity.id) ## Populate events if load_xml_events: eventhandler.read_events(event_folders) ## Make sure DB events are linked. db_events.attach_db_events() ## Create default users. if is_db_empty: dao.store_entity( model.User(cfg.SYSTEM_USER_NAME, None, None, True, None)) UserService().create_user(username=cfg.ADMINISTRATOR_NAME, password=cfg.ADMINISTRATOR_PASSWORD, email=cfg.ADMINISTRATOR_EMAIL, role=ROLE_ADMINISTRATOR) ## In case actions related to latest code-changes are needed, make sure they are executed. CodeUpdateManager().update_all()
class UserServiceTest(TransactionalTestCase): """ This class contains tests for the tvb.core.services.userservice module. """ def setUp(self): """ Reset the database before each test . """ self.clean_database() self.user_service = UserService() self.user_service.create_user(username=cfg.ADMINISTRATOR_NAME, password=cfg.ADMINISTRATOR_PASSWORD, email=cfg.ADMINISTRATOR_EMAIL, role=model.ROLE_ADMINISTRATOR) available_users = dao.get_all_users() if len(available_users) != 1: self.fail("Something went wrong with database initialization!") def tearDown(self): """ Reset database at test finish. """ self.delete_project_folders() def test_create_user_happy_flow(self): """ Standard flow for creating a user. """ initial_user_count = dao.get_all_users() data = dict(username="******", password=md5("test_password").hexdigest(), email="*****@*****.**", role="user", comment="") self.user_service.create_user(**data) final_user_count = dao.get_all_users() self.assertEqual(len(initial_user_count), len(final_user_count) - 1, "User count was not increased after create.") inserted_user = dao.get_user_by_name("test_user") self.assertEqual(inserted_user.password, md5("test_password").hexdigest(), "Incorrect password") self.assertEqual(inserted_user.email, "*****@*****.**", "The email inserted is not correct.") self.assertEqual(inserted_user.role, "user", "The role inserted is not correct.") self.assertFalse(inserted_user.validated, "User validation is not correct.") def test_create_user_empty_password(self): """ Try to create a user with an empty password field. """ data = dict(username="******", password="", email="*****@*****.**", role="user", comment="") self.assertRaises(UsernameException, self.user_service.create_user, **data) def test_create_user_no_password(self): """ Try to create a user with no password data. """ data = dict(username="******", email="*****@*****.**", role="user", comment="") self.assertRaises(UsernameException, self.user_service.create_user, **data) def test_create_user_empty_username(self): """ Try to create a user with an empty username field. """ data = dict(username="", password="******", email="*****@*****.**", role="user", comment="") self.assertRaises(UsernameException, self.user_service.create_user, **data) def test_create_user_no_username(self): """ Try to create a user with no username data. """ data = dict(password="******", email="*****@*****.**", role="user", comment="") self.assertRaises(UsernameException, self.user_service.create_user, **data) def test_create_user_no_email(self): """ Try to create a user with an empty email field. """ data = dict(username="******", password="******", email="", role="user", comment="") self.assertRaises(UsernameException, self.user_service.create_user, **data) def test_reset_password_happy_flow(self): """ Test method for the reset password method. Happy flow. """ data = dict(username="******", password=md5("test_password").hexdigest(), email="*****@*****.**", role="user", comment="") self.user_service.create_user(**data) inserted_user = dao.get_user_by_name("test_user") self.assertEqual(inserted_user.password, md5("test_password").hexdigest(), "Incorrect password") reset_pass_data = dict(username="******", email="*****@*****.**") self.user_service.reset_password(**reset_pass_data) inserted_user = dao.get_user_by_name("test_user") self.assertNotEqual(inserted_user.password, md5("test_password"), "Password not reset for some reason!") def test_reset_pass_wrong_user(self): """ Test method for the reset password method. Username is not valid, should raise exception """ data = dict(username="******", password=md5("test_password").hexdigest(), email="*****@*****.**", role="user", comment="") self.user_service.create_user(**data) inserted_user = dao.get_user_by_name("test_user") self.assertEqual(inserted_user.password, md5("test_password").hexdigest(), "Incorrect password") reset_pass_data = dict(username="******", email="*****@*****.**") self.assertRaises(UsernameException, self.user_service.reset_password, **reset_pass_data) def test_reset_pass_wrong_email(self): """ Test method for the reset password method. Email is not valid, should raise exception """ data = dict(username="******", password=md5("test_password").hexdigest(), email="*****@*****.**", role="user", comment="") self.user_service.create_user(**data) inserted_user = dao.get_user_by_name("test_user") self.assertEqual(inserted_user.password, md5("test_password").hexdigest(), "Incorrect password") reset_pass_data = dict(username="******", email="*****@*****.**") self.assertRaises(UsernameException, self.user_service.reset_password, **reset_pass_data) def test_change_password_happy_flow(self): """ Test method for the change password method. Happy flow. """ inserted_user = self._prepare_user_for_change_pwd() self.user_service.edit_user(inserted_user, md5("test_password").hexdigest()) changed_user = dao.get_user_by_name("test_user") self.assertEqual(changed_user.password, md5("new_test_password").hexdigest(), "The password did not change.") def test_change_password_wrong_old(self): """ Test method for the change password method. Old password is wrong, should return false. """ inserted_user = self._prepare_user_for_change_pwd() params = dict(edited_user=inserted_user, old_password=md5("wrong_old_pwd").hexdigest()) self.assertRaises(UsernameException, self.user_service.edit_user, **params) user = dao.get_user_by_name("test_user") self.assertEqual(user.password, md5("test_password").hexdigest(), "The password should have not been changed!") def _prepare_user_for_change_pwd(self): """Private method to prepare password change operation""" data = dict(username="******", password=md5("test_password").hexdigest(), email="*****@*****.**", role="user", comment="") self.user_service.create_user(**data) self.user_service.validate_user("test_user") inserted_user = dao.get_user_by_name("test_user") self.assertEqual(inserted_user.password, md5("test_password").hexdigest(), "The password inserted is not correct.") inserted_user.password = md5('new_test_password').hexdigest() return inserted_user def test_is_username_valid(self): """ Test the method that checks if a userName is valid or not (if it already exists in the database the userName is not valid). """ user = model.User("test_user", "test_pass", "*****@*****.**", False, "user") dao.store_entity(user) self.assertFalse(self.user_service.is_username_valid("test_user"), "Should be False but got True") self.assertTrue(self.user_service.is_username_valid("test_user2"), "Should be True but got False") def test_validate_user_happy_flow(self): """ Standard flow for a validate user action. """ user = model.User("test_user", "test_pass", "*****@*****.**", False, "user") dao.store_entity(user) self.assertTrue(self.user_service.validate_user("test_user"), "Validation failed when it shouldn't have.") def test_validate_user_validated(self): """ Flow for trying to validate a user that was already validated. """ user = model.User("test_user", "test_pass", "*****@*****.**", True, "user") dao.store_entity(user) self.assertFalse(self.user_service.validate_user("test_user"), "Validation invalid.") def test_validate_user_non_existent(self): """ Flow for trying to validate a user that doesn't exist in the database. """ user = model.User("test_user", "test_pass", "*****@*****.**", True, "user") dao.store_entity(user) self.assertFalse(self.user_service.validate_user("test_user2"), "Validation done even tho user is non-existent") def test_check_login_happy_flow(self): """ Standard login flow with a valid username and password. """ user = model.User("test_user", md5("test_pass").hexdigest(), "*****@*****.**", True, "user") dao.store_entity(user) available_users = dao.get_all_users() if len(available_users) != 2: self.fail("Something went wrong with database reset!") self.assertTrue( self.user_service.check_login("test_user", "test_pass") is not None, "Login failed when it shouldn't.") def test_check_login_bad_pass(self): """ Flow for entering a bad/invalid password. """ user = model.User("test_user", md5("test_pass").hexdigest(), "*****@*****.**", True, "user") dao.store_entity(user) available_users = dao.get_all_users() if len(available_users) != 2: self.fail("Something went wrong with database reset!") self.assertTrue( self.user_service.check_login("test_user", "bad_pass") is None, "Login succeeded with bad password.") def test_check_login_bad_user(self): """ Flow for entering a bad/invalid username. """ user = model.User("test_user", md5("test_pass").hexdigest(), "*****@*****.**", True, "user") dao.store_entity(user) available_users = dao.get_all_users() if len(available_users) != 2: self.fail("Something went wrong with database reset!") self.assertTrue( self.user_service.check_login("bad_user", "test_pass") is None, "Login succeeded with bad userName.") def test_get_users_for_project(self): """ Get all members of a project except the current user. """ user_1 = model.User("test_user1", "test_pass", "*****@*****.**", False, "user") dao.store_entity(user_1) user_2 = model.User("test_user2", "test_pass", "*****@*****.**", False, "user") dao.store_entity(user_2) user_3 = model.User("test_user3", "test_pass", "*****@*****.**", False, "user") dao.store_entity(user_3) user_4 = model.User("test_user4", "test_pass", "*****@*****.**", False, "user") dao.store_entity(user_4) user_5 = model.User("test_user5", "test_pass", "*****@*****.**", False, "user") dao.store_entity(user_5) admin = dao.get_user_by_name("test_user1") member1 = dao.get_user_by_name("test_user2") member2 = dao.get_user_by_name("test_user5") data = dict(name="test_proj", description="test_desc", users=[member1.id, member2.id]) project = ProjectService().store_project(admin, True, None, **data) all_users, members, pag = self.user_service.get_users_for_project( admin.username, project.id) self.assertEquals(len(members), 2, "More members than there should be.") self.assertEquals( len(all_users), 5, "Admin should not be viewed as member. " "Neither should users that were not part of the project's users list." ) self.assertEqual(pag, 1, "Invalid total pages number.") for user in all_users: self.assertNotEqual(user.username, admin.username, "Admin is in members!") def test_get_users_second_page(self): """ Try to get the second page of users for a given project """ for i in range(USERS_PAGE_SIZE + 3): exec 'user_' + str(i) + '= model.User("test_user' + str( i) + '", "test_pass", "*****@*****.**", False, "user")' exec "dao.store_entity(user_" + str(i) + ")" for i in range(USERS_PAGE_SIZE + 3): exec 'member' + str(i) + '=dao.get_user_by_name("test_user' + str( i) + '")' admin = dao.get_user_by_name("test_user1") data = dict(name='test_proj', description='test_desc', users=[ eval('member' + str(i) + '.id') for i in range(USERS_PAGE_SIZE + 3) ]) project = ProjectService().store_project(admin, True, None, **data) page_users, all_users, pag = self.user_service.get_users_for_project( admin.username, project.id, 2) self.assertEqual(len(page_users), (USERS_PAGE_SIZE + 3) % USERS_PAGE_SIZE) self.assertEqual(len(all_users), USERS_PAGE_SIZE + 3, 'Not all members returned') self.assertEqual(pag, 2, 'Invalid page number returned') def test_get_users_second_page_del(self): """ Try to get the second page of users for a given project where only one user on last page. Then delete that user. """ for i in range(USERS_PAGE_SIZE + 1): exec 'user_' + str(i) + '= model.User("test_user' + str(i) + \ '", "test_pass", "*****@*****.**", False, "user")' exec "dao.store_entity(user_" + str(i) + ")" for i in range(USERS_PAGE_SIZE + 1): exec 'member' + str(i) + '=dao.get_user_by_name("test_user' + str( i) + '")' admin = dao.get_user_by_name("test_user1") data = dict(name='test_proj', description='test_desc', users=[ eval('member' + str(i) + '.id') for i in range(USERS_PAGE_SIZE + 1) ]) project = ProjectService().store_project(admin, True, None, **data) page_users, all_users, pag = self.user_service.get_users_for_project( admin.username, project.id, 2) self.assertEqual(len(page_users), 1, 'Paging not working properly') self.assertEqual(len(all_users), USERS_PAGE_SIZE + 1, 'Not all members returned') self.assertEqual(pag, 2, 'Invalid page number returned') self.user_service.delete_user(member2.id) page_users, all_users, pag = self.user_service.get_users_for_project( admin.username, project.id, 2) self.assertEqual(len(page_users), 0, 'Paging not working properly') self.assertEqual(len(all_users), USERS_PAGE_SIZE, 'Not all members returned') self.assertEqual(pag, 1, 'Invalid page number returned') page_users, all_users, pag = self.user_service.get_users_for_project( admin.username, project.id, 1) self.assertEqual(len(page_users), USERS_PAGE_SIZE, 'Paging not working properly') self.assertEqual(len(all_users), USERS_PAGE_SIZE, 'Not all members returned') self.assertEqual(pag, 1, 'Invalid page number returned') def test_edit_user_happy_flow(self): """ Test the method of editing a user. """ data = dict(username="******", password=md5("test_password").hexdigest(), email="*****@*****.**", role="user", comment="") self.user_service.create_user(**data) inserted_user = dao.get_user_by_name("test_user") self.assertEqual(inserted_user.password, md5("test_password").hexdigest(), "Incorrect password") inserted_user.role = "new_role" inserted_user.validated = 1 self.user_service.edit_user(inserted_user) changed_user = dao.get_user_by_name("test_user") self.assertEqual(changed_user.role, "new_role", "role unchanged") self.assertEqual(changed_user.validated, 1, "user not validated") def test_create_project_no_projects(self): """ Standard flow for creating a new project. """ self.assertRaises(UsernameException, self.user_service.get_users_for_project, "admin", 1)
class BaseController(object): """ This class contains the methods served at the root of the Web site. """ def __init__(self): self.logger = get_logger(self.__class__.__module__) self.version_info = None self.user_service = UserService() self.flow_service = FlowService() analyze_category = self.flow_service.get_launchable_non_viewers() self.analyze_category_link = '/flow/step/' + str(analyze_category.id) self.analyze_adapters = None self.connectivity_tab_link = '/flow/step_connectivity' view_category = self.flow_service.get_visualisers_category() conn_id = self.flow_service.get_algorithm_by_module_and_class( CONNECTIVITY_MODULE, CONNECTIVITY_CLASS)[1].id connectivity_link = self.get_url_adapter(view_category.id, conn_id) local_connectivity_link = '/spatial/localconnectivity/step_1/1' connectivity_submenu = [ dict(title="Large Scale Connectivity", subsection="connectivity", description= "View Connectivity Regions. Perform Connectivity lesions", link=connectivity_link), dict(title="Local Connectivity", subsection="local", link=local_connectivity_link, description= "Create or view existent Local Connectivity entities.") ] self.connectivity_submenu = connectivity_submenu @staticmethod def mark_file_for_delete(file_name, delete_parent_folder=False): """ This method stores provided file name in session, and later on when request is done, all these files/folders are deleted :param file_name: name of the file or folder to be deleted :param delete_parent_fodler: specify if the parent folder of the file should be removed too. """ # No processing if no file specified if file_name is None: return files_list = get_from_session(FILES_TO_DELETE_ATTR) if files_list is None: files_list = [] add2session(FILES_TO_DELETE_ATTR, files_list) # Now add file/folder to list if delete_parent_folder: folder_name = os.path.split(file_name)[0] files_list.append(folder_name) else: files_list.append(file_name) def _mark_selected(self, project): """ Set the project passed as parameter as the selected project. """ previous_project = get_current_project() ### Update project stored in selection, with latest Project entity from DB. members = self.user_service.get_users_for_project("", project.id)[1] project.members = members remove_from_session(KEY_CACHED_SIMULATOR_TREE) add2session(KEY_PROJECT, project) if previous_project is None or previous_project.id != project.id: ### Clean Burst selection from session in case of a different project. remove_from_session(KEY_BURST_CONFIG) ### Store in DB new project selection user = get_from_session(KEY_USER) if user is not None: self.user_service.save_project_to_user(user.id, project.id) ### Display info message about project change self.logger.debug("Selected project is now " + project.name) set_info_message("Your current working project is: " + str(project.name)) @staticmethod def get_url_adapter(step_key, adapter_id, back_page=None): """ Compute the URLs for a given adapter. Same URL is used both for GET and POST. """ result_url = '/flow/' + str(step_key) + '/' + str(adapter_id) if back_page is not None: result_url = result_url + "?back_page=" + str(back_page) return result_url @cherrypy.expose def index(self): """ / Path response Redirects to /tvb """ raise cherrypy.HTTPRedirect('/user') @cherrypy.expose() @using_template('user/base_user') def tvb(self, error=False, **data): """ /tvb URL Returns the home page with the messages stored in the user's session. """ self.logger.debug("Unused submit attributes:" + str(data)) template_dictionary = dict(mainContent="../index", title="The Virtual Brain Project") template_dictionary = self._fill_user_specific_attributes( template_dictionary) if get_from_session(KEY_IS_RESTART): template_dictionary[KEY_IS_RESTART] = True remove_from_session(KEY_IS_RESTART) return self.fill_default_attributes(template_dictionary, error) @cherrypy.expose @using_template('user/base_user') def error(self, **data): """Error page to redirect when something extremely bad happened""" template_specification = dict(mainContent="../error", title="Error page", data=data) template_specification = self._fill_user_specific_attributes( template_specification) return self.fill_default_attributes(template_specification) def _populate_user_and_project(self, template_dictionary, escape_db_operations=False): """ Populate the template dictionary with current logged user (from session). """ logged_user = get_logged_user() template_dictionary[KEY_USER] = logged_user show_help = logged_user is not None and logged_user.is_online_help_active( ) template_dictionary[KEY_SHOW_ONLINE_HELP] = show_help project = get_current_project() template_dictionary[KEY_PROJECT] = project if project is not None and not escape_db_operations: self.update_operations_count() return template_dictionary @staticmethod def _populate_message(template_dictionary): """ Populate the template dictionary with current message stored in session. Also specify the message type (default INFO). Clear from session current message (to avoid displaying it twice). """ message_type = remove_from_session(KEY_MESSAGE_TYPE) if message_type is None: message_type = TYPE_INFO template_dictionary[KEY_MESSAGE_TYPE] = message_type message = remove_from_session(KEY_MESSAGE) if message is None: message = "" template_dictionary[KEY_MESSAGE] = message return template_dictionary def _populate_menu(self, template_dictionary): """ Populate current template with information for the Left Menu. """ if KEY_FIRST_RUN not in template_dictionary: template_dictionary[KEY_FIRST_RUN] = False template_dictionary[KEY_LINK_ANALYZE] = self.analyze_category_link template_dictionary[ KEY_LINK_CONNECTIVITY_TAB] = self.connectivity_tab_link if KEY_BACK_PAGE not in template_dictionary: template_dictionary[KEY_BACK_PAGE] = False template_dictionary[ KEY_SECTION_TITLES] = WebStructure.WEB_SECTION_TITLES template_dictionary[ KEY_SUBSECTION_TITLES] = WebStructure.WEB_SUBSECTION_TITLES return template_dictionary def _populate_section(self, algo_group, result_template): """ Populate Section and Sub-Section fields from current Algorithm-Group. """ if algo_group.module == CONNECTIVITY_MODULE: result_template[KEY_SECTION] = 'connectivity' result_template[KEY_SUB_SECTION] = 'connectivity' result_template[KEY_SUBMENU_LIST] = self.connectivity_submenu elif algo_group.group_category.display: ### Visualizers on the Burst Page result_template[KEY_SECTION] = 'burst' result_template[ KEY_SUB_SECTION] = 'view_' + algo_group.subsection_name elif algo_group.group_category.rawinput: ### Upload algorithms result_template[KEY_SECTION] = 'project' result_template[KEY_SUB_SECTION] = 'data' elif 'RAW_DATA' in algo_group.group_category.defaultdatastate: ### Creators result_template[KEY_SECTION] = 'stimulus' result_template[KEY_SUB_SECTION] = 'stimulus' else: ### Analyzers result_template[ KEY_SECTION] = algo_group.group_category.displayname.lower() result_template[KEY_SUB_SECTION] = algo_group.subsection_name result_template[KEY_SUBMENU_LIST] = self.analyze_adapters def _fill_user_specific_attributes(self, template_dictionary): """ Attributes needed for base_user template. """ template_dictionary[KEY_INCLUDE_TOOLTIP] = False template_dictionary[KEY_WRAP_CONTENT_IN_MAIN_DIV] = True template_dictionary[KEY_CURRENT_TAB] = 'none' return template_dictionary def fill_default_attributes(self, template_dictionary, escape_db_operations=False): """ Fill into 'template_dictionary' data that we want to have ready in UI. """ template_dictionary = self._populate_user_and_project( template_dictionary, escape_db_operations) template_dictionary = self._populate_message(template_dictionary) template_dictionary = self._populate_menu(template_dictionary) if KEY_ERRORS not in template_dictionary: template_dictionary[KEY_ERRORS] = {} if KEY_FORM_DATA not in template_dictionary: template_dictionary[KEY_FORM_DATA] = {} if KEY_SUB_SECTION not in template_dictionary and KEY_SECTION in template_dictionary: template_dictionary[KEY_SUB_SECTION] = template_dictionary[ KEY_SECTION] if KEY_SUBMENU_LIST not in template_dictionary: template_dictionary[KEY_SUBMENU_LIST] = None template_dictionary[KEY_CURRENT_VERSION] = cfg.BASE_VERSION return template_dictionary def fill_overlay_attributes(self, template_dictionary, title, description, content_template, css_class, tabs=None, overlay_indexes=None): """ This method prepares parameters for rendering overlay (overlay.html) :param title: overlay title :param description: overlay description :param content_template: path&name of the template file which will fill overlay content (without .html) :param css_class: CSS class to be applied on overlay :param tabs: list of strings containing names of the tabs """ if template_dictionary is None: template_dictionary = dict() template_dictionary[KEY_OVERLAY_TITLE] = title template_dictionary[KEY_OVERLAY_DESCRIPTION] = description template_dictionary[KEY_OVERLAY_CONTENT_TEMPLATE] = content_template template_dictionary[KEY_OVERLAY_CLASS] = css_class template_dictionary[ KEY_OVERLAY_TABS] = tabs if tabs is not None and len( tabs) > 0 else [] if overlay_indexes is not None: template_dictionary[KEY_OVERLAY_INDEXES] = overlay_indexes else: template_dictionary[KEY_OVERLAY_INDEXES] = range( len(tabs)) if tabs is not None else [] template_dictionary[KEY_OVERLAY_PAGINATION] = False return template_dictionary @cherrypy.expose @using_template('overlay_blocker') def showBlockerOverlay(self, **data): """ Returns the content of the blocking overlay (covers entire page and do not allow any action) """ return self.fill_default_attributes(dict(data)) def update_operations_count(self): """ If a project is selected, update Operation Numbers in call-out. """ project = get_current_project() if project is not None: fns, sta, err, canceled = self.flow_service.get_operation_numbers( project.id) project.operations_finished = fns project.operations_started = sta project.operations_error = err project.operations_canceled = canceled add2session(KEY_PROJECT, project)