def _get_candidate_workspace(self, user: User, request: 'TracimRequest') -> Workspace: """ Get current workspace from request :param user: User who want to check the workspace :param request: pyramid request :return: current workspace """ workspace_id = '' try: if 'new_workspace_id' in request.json_body: workspace_id = request.json_body['new_workspace_id'] if not isinstance(workspace_id, int): if workspace_id.isdecimal(): workspace_id = int(workspace_id) else: raise InvalidWorkspaceId( 'workspace_id is not a correct integer') # nopep8 if not workspace_id: raise WorkspaceNotFoundInTracimRequest( 'No new_workspace_id property found in body') # nopep8 wapi = WorkspaceApi(current_user=user, session=request.dbsession, config=request.registry.settings['CFG']) workspace = wapi.get_one(workspace_id) except JSONDecodeError as exc: raise WorkspaceNotFound('Invalid JSON content') from exc except NoResultFound as exc: raise WorkspaceNotFound('Workspace {} does not exist ' 'or is not visible for this user'.format( workspace_id)) from exc return workspace
def delete_comment(self, context, request: TracimRequest, hapic_data=None): """ Delete comment """ app_config = request.registry.settings['CFG'] api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) parent = api.get_one(hapic_data.path.content_id, content_type=ContentType.Any, workspace=workspace) comment = api.get_one( hapic_data.path.comment_id, content_type=ContentType.Comment, workspace=workspace, parent=parent, ) with new_revision(session=request.dbsession, tm=transaction.manager, content=comment): api.delete(comment) return
def _get_current_workspace(self, user: User, request: 'TracimRequest') -> Workspace: """ Get current workspace from request :param user: User who want to check the workspace :param request: pyramid request :return: current workspace """ workspace_id = '' try: if 'workspace_id' in request.matchdict: workspace_id_str = request.matchdict['workspace_id'] if not isinstance( workspace_id_str, str) or not workspace_id_str.isdecimal(): # nopep8 raise InvalidWorkspaceId( 'workspace_id is not a correct integer') # nopep8 workspace_id = int(request.matchdict['workspace_id']) if not workspace_id: raise WorkspaceNotFoundInTracimRequest( 'No workspace_id property found in request') # nopep8 wapi = WorkspaceApi(current_user=user, session=request.dbsession, config=request.registry.settings['CFG']) workspace = wapi.get_one(workspace_id) except NoResultFound as exc: raise WorkspaceNotFound('Workspace {} does not exist ' 'or is not visible for this user'.format( workspace_id)) from exc return workspace
def contents_read_status(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ get user_read status of contents """ app_config = request.registry.settings['CFG'] content_filter = hapic_data.query api = ContentApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = None if hapic_data.path.workspace_id: workspace = wapi.get_one(hapic_data.path.workspace_id) last_actives = api.get_last_active( workspace=workspace, limit=None, before_datetime=None, content_ids=hapic_data.query.contents_ids or None) return [ api.get_content_in_context(content) for content in last_actives ]
def test_func__create_new_content_with_notification__ok__nominal_case(self): uapi = UserApi( current_user=None, session=self.session, config=self.app_config, ) current_user = uapi.get_one_by_email('*****@*****.**') # Create new user with notification enabled on w1 workspace wapi = WorkspaceApi( current_user=current_user, session=self.session, config=self.app_config, ) workspace = wapi.get_one_by_label('Recipes') user = uapi.get_one_by_email('*****@*****.**') wapi.enable_notifications(user, workspace) api = ContentApi( current_user=user, session=self.session, config=self.app_config, ) item = api.create( ContentType.Folder, workspace, None, 'parent', do_save=True, do_notify=False, ) item2 = api.create( ContentType.File, workspace, item, 'file1', do_save=True, do_notify=True, ) # Send mail async from redis queue redis = get_redis_connection( self.app_config ) queue = get_rq_queue( redis, 'mail_sender', ) worker = SimpleWorker([queue], connection=queue.connection) worker.work(burst=True) # check mail received response = requests.get('http://127.0.0.1:8025/api/v1/messages') response = response.json() headers = response[0]['Content']['Headers'] assert headers['From'][0] == '"Bob i. via Tracim" <test_user_from+3@localhost>' # nopep8 assert headers['To'][0] == 'Global manager <*****@*****.**>' assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)' assert headers['References'][0] == 'test_user_refs+22@localhost' assert headers['Reply-to'][0] == '"Bob i. & all members of Recipes" <test_user_reply+22@localhost>' # nopep8
def workspace(self, context, request: TracimRequest, hapic_data=None): """ Get workspace informations """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) return wapi.get_workspace_with_context(request.current_workspace)
def __init__(self, path: str, environ: dict, user: User, session: Session): super(RootResource, self).__init__(path, environ) self.user = user self.session = session # TODO BS 20170221: Web interface should list all workspace to. We # disable it here for moment. When web interface will be updated to # list all workspace, change this here to. self.workspace_api = WorkspaceApi( current_user=self.user, session=session, force_role=True, config=self.provider.app_config )
def test_unit__get_all_manageable(self): admin = self.session.query(User) \ .filter(User.email == '*****@*****.**').one() uapi = UserApi( session=self.session, current_user=admin, config=self.config, ) # Checks a case without workspaces. wapi = WorkspaceApi( session=self.session, current_user=admin, config=self.app_config, ) eq_([], wapi.get_all_manageable()) # Checks an admin gets all workspaces. w4 = wapi.create_workspace(label='w4') w3 = wapi.create_workspace(label='w3') w2 = wapi.create_workspace(label='w2') w1 = wapi.create_workspace(label='w1') eq_([w1, w2, w3, w4], wapi.get_all_manageable()) # Checks a regular user gets none workspace. gapi = GroupApi( session=self.session, current_user=None, config=self.app_config, ) u = uapi.create_minimal_user('[email protected]', [gapi.get_one(Group.TIM_USER)], True) wapi = WorkspaceApi( session=self.session, current_user=u, config=self.app_config, ) rapi = RoleApi( session=self.session, current_user=u, config=self.app_config, ) rapi.create_one(u, w4, UserRoleInWorkspace.READER, False) rapi.create_one(u, w3, UserRoleInWorkspace.CONTRIBUTOR, False) rapi.create_one(u, w2, UserRoleInWorkspace.CONTENT_MANAGER, False) rapi.create_one(u, w1, UserRoleInWorkspace.WORKSPACE_MANAGER, False) eq_([], wapi.get_all_manageable()) # Checks a manager gets only its own workspaces. u.groups.append(gapi.get_one(Group.TIM_MANAGER)) rapi.delete_one(u.user_id, w2.workspace_id) rapi.create_one(u, w2, UserRoleInWorkspace.WORKSPACE_MANAGER, False) eq_([w1, w2], wapi.get_all_manageable())
def create_workspace(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ create workspace """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.create_workspace( label=hapic_data.body.label, description=hapic_data.body.description, save_now=True, ) return wapi.get_workspace_with_context(workspace)
def user_workspace(self, context, request: TracimRequest, hapic_data=None): """ Get list of user workspaces """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspaces = wapi.get_all_for_user(request.candidate_user) return [ wapi.get_workspace_with_context(workspace) for workspace in workspaces ]
def move_folder(self, destpath): workspace_api = WorkspaceApi( current_user=self.user, session=self.session, config=self.provider.app_config, ) workspace = self.provider.get_workspace_from_path( normpath(destpath), workspace_api ) parent = self.provider.get_parent_from_path( normpath(destpath), self.content_api, workspace ) with new_revision( content=self.content, tm=transaction.manager, session=self.session, ): if basename(destpath) != self.getDisplayName(): self.content_api.update_content(self.content, transform_to_bdd(basename(destpath))) self.content_api.save(self.content) else: if workspace.workspace_id == self.content.workspace.workspace_id: self.content_api.move(self.content, parent) else: self.content_api.move_recursively(self.content, parent, workspace) transaction.commit()
def test_get_notifiable_roles(self): admin = self.session.query(User) \ .filter(User.email == '*****@*****.**').one() wapi = WorkspaceApi( session=self.session, config=self.app_config, current_user=admin, ) w = wapi.create_workspace(label='workspace w', save_now=True) uapi = UserApi( session=self.session, current_user=admin, config=self.config ) u = uapi.create_minimal_user(email='[email protected]', save_now=True) eq_([], wapi.get_notifiable_roles(workspace=w)) rapi = RoleApi( session=self.session, current_user=admin, config=self.app_config, ) r = rapi.create_one(u, w, UserRoleInWorkspace.READER, with_notif=True) eq_([r, ], wapi.get_notifiable_roles(workspace=w)) u.is_active = False eq_([], wapi.get_notifiable_roles(workspace=w))
def get_workspace(user: User, request: Request) -> typing.Optional[Workspace]: """ Get current workspace from request :param user: User who want to check the workspace :param request: pyramid request :return: """ workspace_id = '' try: if 'workspace_id' not in request.json_body: return None workspace_id = request.json_body['workspace_id'] wapi = WorkspaceApi(current_user=user, session=request.dbsession) workspace = wapi.get_one(workspace_id) except JSONDecodeError: raise WorkspaceNotFound('Bad json body') except NoResultFound: raise WorkspaceNotFound( 'Workspace {} does not exist ' 'or is not visible for this user'.format(workspace_id) ) return workspace
def exists(self, path, environ) -> bool: """ Called by wsgidav to check if a certain path is linked to a _DAVResource """ path = normpath(path) working_path = self.reduce_path(path) root_path = environ['http_authenticator.realm'] parent_path = dirname(working_path) user = environ['tracim_user'] session = environ['tracim_dbsession'] if path == root_path: return True workspace = self.get_workspace_from_path( path, WorkspaceApi( current_user=user, session=session, config=self.app_config, ) ) if parent_path == root_path or workspace is None: return workspace is not None # TODO bastien: Arnaud avait mis a True, verif le comportement # lorsque l'on explore les dossiers archive et deleted content_api = ContentApi( current_user=user, session=session, config=self.app_config, show_archived=False, show_deleted=False ) revision_id = re.search(r'/\.history/[^/]+/\((\d+) - [a-zA-Z]+\) ([^/].+)$', path) is_archived = self.is_path_archive(path) is_deleted = self.is_path_delete(path) if revision_id: revision_id = revision_id.group(1) content = content_api.get_one_revision(revision_id) else: content = self.get_content_from_path(working_path, content_api, workspace) return content is not None \ and content.is_deleted == is_deleted \ and content.is_archived == is_archived
def copyMoveSingle(self, destpath, isMove): if isMove: # INFO - G.M - 12-03-2018 - This case should not happen # As far as moveRecursive method exist, all move should not go # through this method. If such case appear, try replace this to : #### # self.move_file(destpath) # return #### raise NotImplemented new_file_name = None new_file_extension = None # Inspect destpath if basename(destpath) != self.getDisplayName(): new_given_file_name = transform_to_bdd(basename(destpath)) new_file_name, new_file_extension = \ os.path.splitext(new_given_file_name) workspace_api = WorkspaceApi( current_user=self.user, session=self.session, config=self.provider.app_config, ) content_api = ContentApi( current_user=self.user, session=self.session, config=self.provider.app_config ) destination_workspace = self.provider.get_workspace_from_path( destpath, workspace_api, ) destination_parent = self.provider.get_parent_from_path( destpath, content_api, destination_workspace, ) workspace = self.content.workspace parent = self.content.parent new_content = self.content_api.copy( item=self.content, new_label=new_file_name, new_parent=destination_parent, ) self.content_api.copy_children(self.content, new_content) transaction.commit()
def _create_workspace_and_test(self, name, user) -> Workspace: """ All extra parameters (*args, **kwargs) are for Workspace init :return: Created workspace instance """ WorkspaceApi( current_user=user, session=self.session, ).create_workspace(name, save_now=True) eq_( 1, self.session.query(Workspace).filter( Workspace.label == name).count()) return self.session.query(Workspace).filter( Workspace.label == name).one()
def get_workspace_from_path(self, path: str, api: WorkspaceApi) -> Workspace: try: return api.get_one_by_label(transform_to_bdd(path.split('/')[1])) except NoResultFound: return None
def notify_content_update( self, event_actor_id: int, event_content_id: int ) -> None: """ Look for all users to be notified about the new content and send them an individual email :param event_actor_id: id of the user that has triggered the event :param event_content_id: related content_id :return: """ # FIXME - D.A. - 2014-11-05 # Dirty import. It's here in order to avoid circular import from tracim.lib.core.content import ContentApi from tracim.lib.core.user import UserApi user = UserApi( None, config=self.config, session=self.session, ).get_one(event_actor_id) logger.debug(self, 'Content: {}'.format(event_content_id)) content_api = ContentApi( current_user=user, session=self.session, config=self.config, ) content = ContentApi( session=self.session, current_user=user, # TODO - use a system user instead of the user that has triggered the event config=self.config, show_archived=True, show_deleted=True, ).get_one(event_content_id, ContentType.Any) main_content = content.parent if content.type == ContentType.Comment else content notifiable_roles = WorkspaceApi( current_user=user, session=self.session, config=self.config, ).get_notifiable_roles(content.workspace) if len(notifiable_roles) <= 0: logger.info(self, 'Skipping notification as nobody subscribed to in workspace {}'.format(content.workspace.label)) return logger.info(self, 'Sending asynchronous emails to {} user(s)'.format(len(notifiable_roles))) # INFO - D.A. - 2014-11-06 # The following email sender will send emails in the async task queue # This allow to build all mails through current thread but really send them (including SMTP connection) # In the other thread. # # This way, the webserver will return sooner (actually before notification emails are sent async_email_sender = EmailSender( self.config, self._smtp_config, self.config.EMAIL_NOTIFICATION_ACTIVATED ) for role in notifiable_roles: logger.info(self, 'Sending email to {}'.format(role.user.email)) to_addr = formataddr((role.user.display_name, role.user.email)) # # INFO - G.M - 2017-11-15 - set content_id in header to permit reply # references can have multiple values, but only one in this case. replyto_addr = self.config.EMAIL_NOTIFICATION_REPLY_TO_EMAIL.replace( # nopep8 '{content_id}',str(content.content_id) ) reference_addr = self.config.EMAIL_NOTIFICATION_REFERENCES_EMAIL.replace( #nopep8 '{content_id}',str(content.content_id) ) # # INFO - D.A. - 2014-11-06 # We do not use .format() here because the subject defined in the .ini file # may not include all required labels. In order to avoid partial format() (which result in an exception) # we do use replace and force the use of .__str__() in order to process LazyString objects # subject = self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT subject = subject.replace(EST.WEBSITE_TITLE, self.config.WEBSITE_TITLE.__str__()) subject = subject.replace(EST.WORKSPACE_LABEL, main_content.workspace.label.__str__()) subject = subject.replace(EST.CONTENT_LABEL, main_content.label.__str__()) subject = subject.replace(EST.CONTENT_STATUS_LABEL, main_content.get_status().label.__str__()) reply_to_label = l_('{username} & all members of {workspace}').format( username=user.display_name, workspace=main_content.workspace.label) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._get_sender(user) message['To'] = to_addr message['Reply-to'] = formataddr((reply_to_label, replyto_addr)) # INFO - G.M - 2017-11-15 # References can theorically have label, but in pratice, references # contains only message_id from parents post in thread. # To link this email to a content we create a virtual parent # in reference who contain the content_id. message['References'] = formataddr(('',reference_addr)) body_text = self._build_email_body_for_content(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content, user) body_html = self._build_email_body_for_content(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content, user) part1 = MIMEText(body_text, 'plain', 'utf-8') part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, in this case # the HTML message, is best and preferred. message.attach(part1) message.attach(part2) self.log_notification( action='CREATED', recipient=message['To'], subject=message['Subject'], config=self.config, ) send_email_through( self.config, async_email_sender.send_mail, message )
def insert(self): admin = self._session.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() bob = self._session.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() john_the_reader = self._session.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() admin_workspace_api = WorkspaceApi( current_user=admin, session=self._session, config=self._config, ) bob_workspace_api = WorkspaceApi(current_user=bob, session=self._session, config=self._config) content_api = ContentApi(current_user=admin, session=self._session, config=self._config) bob_content_api = ContentApi(current_user=bob, session=self._session, config=self._config) reader_content_api = ContentApi(current_user=john_the_reader, session=self._session, config=self._config) role_api = RoleApi( current_user=admin, session=self._session, config=self._config, ) # Workspaces business_workspace = admin_workspace_api.create_workspace( 'Business', description='All importants documents', save_now=True, ) recipe_workspace = admin_workspace_api.create_workspace( 'Recipes', description='Our best recipes', save_now=True, ) other_workspace = bob_workspace_api.create_workspace( 'Others', description='Other Workspace', save_now=True, ) # Workspaces roles role_api.create_one( user=bob, workspace=recipe_workspace, role_level=UserRoleInWorkspace.CONTENT_MANAGER, with_notif=False, ) role_api.create_one( user=john_the_reader, workspace=recipe_workspace, role_level=UserRoleInWorkspace.READER, with_notif=False, ) # Folders tool_workspace = content_api.create( content_type=ContentType.Folder, workspace=business_workspace, label='Tools', do_save=True, do_notify=False, ) menu_workspace = content_api.create( content_type=ContentType.Folder, workspace=business_workspace, label='Menus', do_save=True, do_notify=False, ) dessert_folder = content_api.create( content_type=ContentType.Folder, workspace=recipe_workspace, label='Desserts', do_save=True, do_notify=False, ) salads_folder = content_api.create( content_type=ContentType.Folder, workspace=recipe_workspace, label='Salads', do_save=True, do_notify=False, ) other_folder = content_api.create( content_type=ContentType.Folder, workspace=other_workspace, label='Infos', do_save=True, do_notify=False, ) # Pages, threads, .. tiramisu_page = content_api.create( content_type=ContentType.Page, workspace=recipe_workspace, parent=dessert_folder, label='Tiramisu Recipes!!!', do_save=True, do_notify=False, ) with new_revision( session=self._session, tm=transaction.manager, content=tiramisu_page, ): content_api.update_content( item=tiramisu_page, new_content= '<p>To cook a greet Tiramisu, you need many ingredients.</p>', # nopep8 new_label='Tiramisu Recipes!!!', ) content_api.save(tiramisu_page) best_cake_thread = content_api.create( content_type=ContentType.Thread, workspace=recipe_workspace, parent=dessert_folder, label='Best Cake', do_save=False, do_notify=False, ) best_cake_thread.description = 'Which is the best cake?' self._session.add(best_cake_thread) apple_pie_recipe = content_api.create( content_type=ContentType.File, workspace=recipe_workspace, parent=dessert_folder, label='Apple_Pie', do_save=False, do_notify=False, ) apple_pie_recipe.file_extension = '.txt' apple_pie_recipe.depot_file = FileIntent( b'Apple pie Recipe', 'apple_Pie.txt', 'text/plain', ) self._session.add(apple_pie_recipe) Brownie_recipe = content_api.create( content_type=ContentType.File, workspace=recipe_workspace, parent=dessert_folder, label='Brownie Recipe', do_save=False, do_notify=False, ) Brownie_recipe.file_extension = '.html' Brownie_recipe.depot_file = FileIntent( b'<p>Brownie Recipe</p>', 'brownie_recipe.html', 'text/html', ) self._session.add(Brownie_recipe) fruits_desserts_folder = content_api.create( content_type=ContentType.Folder, workspace=recipe_workspace, label='Fruits Desserts', parent=dessert_folder, do_save=True, ) menu_page = content_api.create( content_type=ContentType.Page, workspace=business_workspace, parent=menu_workspace, label='Current Menu', do_save=True, ) new_fruit_salad = content_api.create( content_type=ContentType.Page, workspace=recipe_workspace, parent=fruits_desserts_folder, label='New Fruit Salad', do_save=True, ) old_fruit_salad = content_api.create( content_type=ContentType.Page, workspace=recipe_workspace, parent=fruits_desserts_folder, label='Fruit Salad', do_save=True, do_notify=False, ) with new_revision( session=self._session, tm=transaction.manager, content=old_fruit_salad, ): content_api.archive(old_fruit_salad) content_api.save(old_fruit_salad) bad_fruit_salad = content_api.create( content_type=ContentType.Page, workspace=recipe_workspace, parent=fruits_desserts_folder, label='Bad Fruit Salad', do_save=True, do_notify=False, ) with new_revision( session=self._session, tm=transaction.manager, content=bad_fruit_salad, ): content_api.delete(bad_fruit_salad) content_api.save(bad_fruit_salad) # File at the root for test new_fruit_salad = content_api.create( content_type=ContentType.Page, workspace=other_workspace, label='New Fruit Salad', do_save=True, ) old_fruit_salad = content_api.create( content_type=ContentType.Page, workspace=other_workspace, label='Fruit Salad', do_save=True, ) with new_revision( session=self._session, tm=transaction.manager, content=old_fruit_salad, ): content_api.archive(old_fruit_salad) content_api.save(old_fruit_salad) bad_fruit_salad = content_api.create( content_type=ContentType.Page, workspace=other_workspace, label='Bad Fruit Salad', do_save=True, ) with new_revision( session=self._session, tm=transaction.manager, content=bad_fruit_salad, ): content_api.delete(bad_fruit_salad) content_api.save(bad_fruit_salad) content_api.create_comment( parent=best_cake_thread, content= '<p>What is for you the best cake ever? </br> I personnally vote for Chocolate cupcake!</p>', # nopep8 do_save=True, ) bob_content_api.create_comment( parent=best_cake_thread, content='<p>What about Apple Pie? There are Awesome!</p>', do_save=True, ) reader_content_api.create_comment( parent=best_cake_thread, content= '<p>You are right, but Kouign-amann are clearly better.</p>', do_save=True, ) with new_revision( session=self._session, tm=transaction.manager, content=best_cake_thread, ): bob_content_api.update_content( item=best_cake_thread, new_content='What is the best cake?', new_label='Best Cakes?', ) bob_content_api.save(best_cake_thread) with new_revision( session=self._session, tm=transaction.manager, content=tiramisu_page, ): bob_content_api.update_content( item=tiramisu_page, new_content= '<p>To cook a great Tiramisu, you need many ingredients.</p>', # nopep8 new_label='Tiramisu Recipe', ) bob_content_api.save(tiramisu_page) self._session.flush()
def insert(self): admin = self._session.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() bob = self._session.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() admin_workspace_api = WorkspaceApi( current_user=admin, session=self._session, ) bob_workspace_api = WorkspaceApi( current_user=bob, session=self._session, ) content_api = ContentApi( current_user=admin, session=self._session, ) role_api = RoleApi( current_user=admin, session=self._session, ) # Workspaces w1 = admin_workspace_api.create_workspace('w1', save_now=True) w2 = bob_workspace_api.create_workspace('w2', save_now=True) w3 = admin_workspace_api.create_workspace('w3', save_now=True) # Workspaces roles role_api.create_one( user=bob, workspace=w1, role_level=UserRoleInWorkspace.CONTENT_MANAGER, with_notif=False, ) # Folders w1f1 = content_api.create( content_type=ContentType.Folder, workspace=w1, label='w1f1', do_save=True, ) w1f2 = content_api.create( content_type=ContentType.Folder, workspace=w1, label='w1f2', do_save=True, ) w2f1 = content_api.create( content_type=ContentType.Folder, workspace=w2, label='w2f1', do_save=True, ) w2f2 = content_api.create( content_type=ContentType.Folder, workspace=w2, label='w2f2', do_save=True, ) w3f1 = content_api.create( content_type=ContentType.Folder, workspace=w3, label='w3f3', do_save=True, ) # Pages, threads, .. w1f1p1 = content_api.create( content_type=ContentType.Page, workspace=w1, parent=w1f1, label='w1f1p1', do_save=True, ) w1f1t1 = content_api.create( content_type=ContentType.Thread, workspace=w1, parent=w1f1, label='w1f1t1', do_save=False, ) w1f1t1.description = 'w1f1t1 description' self._session.add(w1f1t1) w1f1d1_txt = content_api.create( content_type=ContentType.File, workspace=w1, parent=w1f1, label='w1f1d1', do_save=False, ) w1f1d1_txt.file_extension = '.txt' w1f1d1_txt.depot_file = FileIntent( b'w1f1d1 content', 'w1f1d1.txt', 'text/plain', ) self._session.add(w1f1d1_txt) w1f1d2_html = content_api.create( content_type=ContentType.File, workspace=w1, parent=w1f1, label='w1f1d2', do_save=False, ) w1f1d2_html.file_extension = '.html' w1f1d2_html.depot_file = FileIntent( b'<p>w1f1d2 content</p>', 'w1f1d2.html', 'text/html', ) self._session.add(w1f1d2_html) w1f1f1 = content_api.create( content_type=ContentType.Folder, workspace=w1, label='w1f1f1', parent=w1f1, do_save=True, ) w2f1p1 = content_api.create( content_type=ContentType.Page, workspace=w2, parent=w2f1, label='w2f1p1', do_save=True, ) self._session.flush()
def move_file(self, destpath: str) -> None: """ Move file mean changing the path to access to a file. This can mean simple renaming(1), moving file from a directory to one another(2) but also renaming + moving file from a directory to one another at the same time (3). (1): move /dir1/file1 -> /dir1/file2 (2): move /dir1/file1 -> /dir2/file1 (3): move /dir1/file1 -> /dir2/file2 :param destpath: destination path of webdav move :return: nothing """ workspace = self.content.workspace parent = self.content.parent with new_revision( content=self.content, tm=transaction.manager, session=self.session, ): # INFO - G.M - 2018-03-09 - First, renaming file if needed if basename(destpath) != self.getDisplayName(): new_given_file_name = transform_to_bdd(basename(destpath)) new_file_name, new_file_extension = \ os.path.splitext(new_given_file_name) self.content_api.update_content( self.content, new_file_name, ) self.content.file_extension = new_file_extension self.content_api.save(self.content) # INFO - G.M - 2018-03-09 - Moving file if needed workspace_api = WorkspaceApi( current_user=self.user, session=self.session, config=self.provider.app_config, ) content_api = ContentApi( current_user=self.user, session=self.session, config=self.provider.app_config ) destination_workspace = self.provider.get_workspace_from_path( destpath, workspace_api, ) destination_parent = self.provider.get_parent_from_path( destpath, content_api, destination_workspace, ) if destination_parent != parent or destination_workspace != workspace: # nopep8 # INFO - G.M - 12-03-2018 - Avoid moving the file "at the same place" # nopep8 # if the request does not result in a real move. self.content_api.move( item=self.content, new_parent=destination_parent, must_stay_in_same_workspace=False, new_workspace=destination_workspace ) transaction.commit()
def getResourceInst(self, path: str, environ: dict): """ Called by wsgidav whenever a request is called to get the _DAVResource corresponding to the path """ user = environ['tracim_user'] session = environ['tracim_dbsession'] if not self.exists(path, environ): return None path = normpath(path) root_path = environ['http_authenticator.realm'] # If the requested path is the root, then we return a RootResource resource if path == root_path: return resources.RootResource( path=path, environ=environ, user=user, session=session ) workspace_api = WorkspaceApi( current_user=user, session=session, config=self.app_config, ) workspace = self.get_workspace_from_path(path, workspace_api) # If the request path is in the form root/name, then we return a WorkspaceResource resource parent_path = dirname(path) if parent_path == root_path: if not workspace: return None return resources.WorkspaceResource( path=path, environ=environ, workspace=workspace, user=user, session=session, ) # And now we'll work on the path to establish which type or resource is requested content_api = ContentApi( current_user=user, session=session, config=self.app_config, show_archived=False, # self._show_archive, show_deleted=False, # self._show_delete ) content = self.get_content_from_path( path=path, content_api=content_api, workspace=workspace ) # Easy cases : path either end with /.deleted, /.archived or /.history, then we return corresponding resources if path.endswith(SpecialFolderExtension.Archived) and self._show_archive: return resources.ArchivedFolderResource( path=path, environ=environ, workspace=workspace, user=user, content=content, session=session, ) if path.endswith(SpecialFolderExtension.Deleted) and self._show_delete: return resources.DeletedFolderResource( path=path, environ=environ, workspace=workspace, user=user, content=content, session=session, ) if path.endswith(SpecialFolderExtension.History) and self._show_history: is_deleted_folder = re.search(r'/\.deleted/\.history$', path) is not None is_archived_folder = re.search(r'/\.archived/\.history$', path) is not None type = HistoryType.Deleted if is_deleted_folder \ else HistoryType.Archived if is_archived_folder \ else HistoryType.Standard return resources.HistoryFolderResource( path=path, environ=environ, workspace=workspace, user=user, content=content, session=session, type=type ) # Now that's more complicated, we're trying to find out if the path end with /.history/file_name is_history_file_folder = re.search(r'/\.history/([^/]+)$', path) is not None if is_history_file_folder and self._show_history: return resources.HistoryFileFolderResource( path=path, environ=environ, user=user, content=content, session=session, ) # And here next step : is_history_file = re.search(r'/\.history/[^/]+/\((\d+) - [a-zA-Z]+\) .+', path) is not None if self._show_history and is_history_file: revision_id = re.search(r'/\.history/[^/]+/\((\d+) - [a-zA-Z]+\) ([^/].+)$', path).group(1) content_revision = content_api.get_one_revision(revision_id) content = self.get_content_from_revision(content_revision, content_api) if content.type == ContentType.File: return resources.HistoryFileResource( path=path, environ=environ, user=user, content=content, content_revision=content_revision, session=session, ) else: return resources.HistoryOtherFile( path=path, environ=environ, user=user, content=content, content_revision=content_revision, session=session, ) # And if we're still going, the client is asking for a standard Folder/File/Page/Thread so we check the type7 # and return the corresponding resource if content is None: return None if content.type == ContentType.Folder: return resources.FolderResource( path=path, environ=environ, workspace=content.workspace, content=content, session=session, user=user, ) elif content.type == ContentType.File: return resources.FileResource( path=path, environ=environ, content=content, session=session, user=user ) else: return resources.OtherFileResource( path=path, environ=environ, content=content, session=session, user=user, )
class RootResource(DAVCollection): """ RootResource ressource that represents tracim's home, which contains all workspaces """ def __init__(self, path: str, environ: dict, user: User, session: Session): super(RootResource, self).__init__(path, environ) self.user = user self.session = session # TODO BS 20170221: Web interface should list all workspace to. We # disable it here for moment. When web interface will be updated to # list all workspace, change this here to. self.workspace_api = WorkspaceApi( current_user=self.user, session=session, force_role=True, config=self.provider.app_config ) def __repr__(self) -> str: return '<DAVCollection: RootResource>' def getMemberNames(self) -> [str]: """ This method returns the names (here workspace's labels) of all its children Though for perfomance issue, we're not using this function anymore """ return [workspace.label for workspace in self.workspace_api.get_all()] def getMember(self, label: str) -> DAVCollection: """ This method returns the child Workspace that corresponds to a given name Though for perfomance issue, we're not using this function anymore """ try: workspace = self.workspace_api.get_one_by_label(label) workspace_path = '%s%s%s' % (self.path, '' if self.path == '/' else '/', transform_to_display(workspace.label)) return WorkspaceResource( workspace_path, self.environ, workspace, session=self.session, user=self.user, ) except AttributeError: return None def createEmptyResource(self, name: str): """ This method is called whenever the user wants to create a DAVNonCollection resource (files in our case). There we don't allow to create files at the root; only workspaces (thus collection) can be created. """ raise DAVError(HTTP_FORBIDDEN) def createCollection(self, name: str): """ This method is called whenever the user wants to create a DAVCollection resource as a child (in our case, we create workspaces as this is the root). [For now] we don't allow to create new workspaces through webdav client. Though if we come to allow it, deleting the error's raise will make it possible. """ # TODO : remove comment here # raise DAVError(HTTP_FORBIDDEN) new_workspace = self.workspace_api.create_workspace(name) self.workspace_api.save(new_workspace) workspace_path = '%s%s%s' % ( self.path, '' if self.path == '/' else '/', transform_to_display(new_workspace.label)) transaction.commit() return WorkspaceResource( workspace_path, self.environ, new_workspace, user=self.user, session=self.session, ) def getMemberList(self): """ This method is called by wsgidav when requesting with a depth > 0, it will return a list of _DAVResource of all its direct children """ members = [] for workspace in self.workspace_api.get_all(): workspace_path = '%s%s%s' % (self.path, '' if self.path == '/' else '/', workspace.label) members.append( WorkspaceResource( path=workspace_path, environ=self.environ, workspace=workspace, user=self.user, session=self.session, ) ) return members