def _c(): log.debug('generating switcher repo/groups list') all_repos = Repository.query(sorted=True).all() repo_iter = self.scm_model.get_repos(all_repos) all_groups = RepoGroup.query(sorted=True).all() repo_groups_iter = self.scm_model.get_repo_groups(all_groups) res = [{ 'text': _('Groups'), 'children': [ {'id': obj.group_name, 'text': obj.group_name, 'type': 'group', 'obj': {}} for obj in repo_groups_iter ], }, { 'text': _('Repositories'), 'children': [ {'id': obj.repo_name, 'text': obj.repo_name, 'type': 'repo', 'obj': obj.get_dict()} for obj in repo_iter ], }] data = { 'more': False, 'results': res, } return data
def my_account_password(self): c.active = 'password' self.__load_data() managed_fields = auth_modules.get_managed_fields(c.user) c.can_change_password = '******' not in managed_fields if request.POST and c.can_change_password: _form = PasswordChangeForm(request.authuser.username)() try: form_result = _form.to_python(request.POST) UserModel().update(request.authuser.user_id, form_result) Session().commit() h.flash(_("Successfully updated password"), category='success') except formencode.Invalid as errors: return htmlfill.render( render('admin/my_account/my_account.html'), defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8", force_defaults=False) except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during update of user password'), category='error') return render('admin/my_account/my_account.html')
def lnk(rev, repo_name): lazy_cs = False title_ = None url_ = '#' if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict): if rev.op and rev.ref_name: if rev.op == 'delete_branch': lbl = _('Deleted branch: %s') % rev.ref_name elif rev.op == 'tag': lbl = _('Created tag: %s') % rev.ref_name else: lbl = 'Unknown operation %s' % rev.op else: lazy_cs = True lbl = rev.short_id[:8] url_ = url('changeset_home', repo_name=repo_name, revision=rev.raw_id) else: # changeset cannot be found - it might have been stripped or removed lbl = rev[:12] title_ = _('Changeset %s not found') % lbl if parse_cs: return link_to(lbl, url_, title=title_, **{'data-toggle': 'tooltip'}) return link_to(lbl, url_, class_='lazy-cs' if lazy_cs else '', **{'data-raw_id':rev.raw_id, 'data-repo_name':repo_name})
def repo_refs_data(self, repo_name): repo = Repository.get_by_repo_name(repo_name).scm_instance res = [] _branches = repo.branches.items() if _branches: res.append({ 'text': _('Branch'), 'children': [{'id': rev, 'text': name, 'type': 'branch'} for name, rev in _branches] }) _closed_branches = repo.closed_branches.items() if _closed_branches: res.append({ 'text': _('Closed Branches'), 'children': [{'id': rev, 'text': name, 'type': 'closed-branch'} for name, rev in _closed_branches] }) _tags = repo.tags.items() if _tags: res.append({ 'text': _('Tag'), 'children': [{'id': rev, 'text': name, 'type': 'tag'} for name, rev in _tags] }) _bookmarks = repo.bookmarks.items() if _bookmarks: res.append({ 'text': _('Bookmark'), 'children': [{'id': rev, 'text': name, 'type': 'book'} for name, rev in _bookmarks] }) data = { 'more': False, 'results': res } return data
def create(self): c.default_extern_type = User.DEFAULT_AUTH_TYPE c.default_extern_name = '' user_model = UserModel() user_form = UserForm()() try: form_result = user_form.to_python(dict(request.POST)) user = user_model.create(form_result) action_logger(request.authuser, 'admin_created_user:%s' % user.username, None, request.ip_addr) h.flash(_('Created user %s') % user.username, category='success') Session().commit() except formencode.Invalid as errors: return htmlfill.render( render('admin/users/user_add.html'), defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8", force_defaults=False) except UserCreationError as e: h.flash(e, 'error') except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during creation of user %s') \ % request.POST.get('username'), category='error') raise HTTPFound(location=url('edit_user', id=user.user_id))
def delete(self, category_id, **kw): try: app_globals.shop.category.delete(category_id) flash(_('Category deleted')) except CategoryAssignedToProductException: flash(_('Is impossible to delete a category assigned to product'), 'error') return redirect(plug_url('stroller2', '/manage/category/index'))
def delete(self, user, cur_user=None): if cur_user is None: cur_user = getattr(get_current_authuser(), 'username', None) user = User.guess_instance(user) if user.is_default_user: raise DefaultUserException( _("You can't remove this user since it is" " crucial for the entire application")) if user.repositories: repos = [x.repo_name for x in user.repositories] raise UserOwnsReposException( _('User "%s" still owns %s repositories and cannot be ' 'removed. Switch owners or remove those repositories: %s') % (user.username, len(repos), ', '.join(repos))) if user.repo_groups: repogroups = [x.group_name for x in user.repo_groups] raise UserOwnsReposException(_( 'User "%s" still owns %s repository groups and cannot be ' 'removed. Switch owners or remove those repository groups: %s') % (user.username, len(repogroups), ', '.join(repogroups))) if user.user_groups: usergroups = [x.users_group_name for x in user.user_groups] raise UserOwnsReposException( _('User "%s" still owns %s user groups and cannot be ' 'removed. Switch owners or remove those user groups: %s') % (user.username, len(usergroups), ', '.join(usergroups))) Session().delete(user) from kallithea.lib.hooks import log_delete_user log_delete_user(user.get_dict(), cur_user)
def activate(self, email, code): reg = Registration.get_inactive(email, code) if not reg: flash(_('Registration not found or already activated')) return redirect(self.mount_point) u = app_model.User(user_name=reg.user_name, display_name=reg.user_name, email_address=reg.email_address, password=reg.password) hooks = config['hooks'].get('registration.before_activation', []) for func in hooks: func(reg, u) DBSession.add(u) reg.user = u reg.password = '******' reg.activated = datetime.now() hooks = config['hooks'].get('registration.after_activation', []) for func in hooks: func(reg, u) flash(_('Account succesfully activated')) return redirect('/')
def update_perms(self, group_name): """ Update permissions for given repository group :param group_name: """ c.repo_group = RepoGroup.guess_instance(group_name) valid_recursive_choices = ['none', 'repos', 'groups', 'all'] form_result = RepoGroupPermsForm(valid_recursive_choices)().to_python(request.POST) if not request.authuser.is_admin: if self._revoke_perms_on_yourself(form_result): msg = _('Cannot revoke permission for yourself as admin') h.flash(msg, category='warning') raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name)) recursive = form_result['recursive'] # iterate over all members(if in recursive mode) of this groups and # set the permissions ! # this can be potentially heavy operation RepoGroupModel()._update_permissions(c.repo_group, form_result['perms_new'], form_result['perms_updates'], recursive) #TODO: implement this #action_logger(request.authuser, 'admin_changed_repo_permissions', # repo_name, request.ip_addr) Session().commit() h.flash(_('Repository group permissions updated'), category='success') raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name))
def delete_perms(self, id): try: obj_type = request.POST.get('obj_type') obj_id = None if obj_type == 'user': obj_id = safe_int(request.POST.get('user_id')) elif obj_type == 'user_group': obj_id = safe_int(request.POST.get('user_group_id')) if not request.authuser.is_admin: if obj_type == 'user' and request.authuser.user_id == obj_id: msg = _('Cannot revoke permission for yourself as admin') h.flash(msg, category='warning') raise Exception('revoke admin permission on self') if obj_type == 'user': UserGroupModel().revoke_user_permission(user_group=id, user=obj_id) elif obj_type == 'user_group': UserGroupModel().revoke_user_group_permission(target_user_group=id, user_group=obj_id) Session().commit() except Exception: log.error(traceback.format_exc()) h.flash(_('An error occurred during revoking of permission'), category='error') raise HTTPInternalServerError()
def put_delete_undo(self, item_id): require_current_user_is_owner(int(item_id)) item_id = int(item_id) content_api = ContentApi(tmpl_context.current_user, True, True) # Here we do not filter deleted items item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace) try: next_url = tg.url('/workspaces/{}/folders/{}/threads/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, tmpl_context.thread_id) msg = _('{} undeleted.').format(self._item_type_label) content_api.undelete(item) content_api.save(item, ActionDescription.UNDELETION) tg.flash(msg, CST.STATUS_OK) tg.redirect(next_url) except ValueError as e: logger.debug(self, 'Exception: {}'.format(e.__str__)) back_url = tg.url('/workspaces/{}/folders/{}/threads/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, tmpl_context.thread_id) msg = _('{} not un-deleted: {}').format(self._item_type_label, str(e)) tg.flash(msg, CST.STATUS_ERROR) tg.redirect(back_url)
def create(self): users_group_form = UserGroupForm()() try: form_result = users_group_form.to_python(dict(request.POST)) ug = UserGroupModel().create(name=form_result['users_group_name'], description=form_result['user_group_description'], owner=request.authuser.user_id, active=form_result['users_group_active']) gr = form_result['users_group_name'] action_logger(request.authuser, 'admin_created_users_group:%s' % gr, None, request.ip_addr) h.flash(h.literal(_('Created user group %s') % h.link_to(h.escape(gr), url('edit_users_group', id=ug.users_group_id))), category='success') Session().commit() except formencode.Invalid as errors: return htmlfill.render( render('admin/user_groups/user_group_add.html'), defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8", force_defaults=False) except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during creation of user group %s') \ % request.POST.get('users_group_name'), category='error') raise HTTPFound(location=url('users_groups'))
def put(self, current_password, new_password1, new_password2): if not tg.config.get('auth_is_internal'): raise HTTPForbidden() # FIXME - Allow only self password or operation for managers current_user = tmpl_context.current_user redirect_url = tg.lurl('/home') if not current_password or not new_password1 or not new_password2: tg.flash(_('Empty password is not allowed.')) tg.redirect(redirect_url) if current_user.validate_password(current_password) is False: tg.flash(_('The current password you typed is wrong')) tg.redirect(redirect_url) if new_password1!=new_password2: tg.flash(_('New passwords do not match.')) tg.redirect(redirect_url) current_user.password = new_password1 pm.DBSession.flush() tg.flash(_('Your password has been changed')) tg.redirect(redirect_url)
def put(self, user_id, name, email, timezone, next_url=None): user_id = tmpl_context.current_user.user_id current_user = tmpl_context.current_user user_api = UserApi(current_user) assert user_id==current_user.user_id if next_url: next = tg.url(next_url) else: next = self.url() try: email_user = user_api.get_one_by_email(email) if email_user != current_user: tg.flash(_('Email already in use'), CST.STATUS_ERROR) tg.redirect(next) except NoResultFound: pass # Only keep allowed field update updated_fields = self._clean_update_fields({ 'name': name, 'email': email, 'timezone': timezone, }) api = UserApi(tmpl_context.current_user) api.update(current_user, do_save=True, **updated_fields) tg.flash(_('profile updated.')) tg.redirect(next)
def delete(self, repo_name): repo_model = RepoModel() repo = repo_model.get_by_repo_name(repo_name) if not repo: h.not_mapped_error(repo_name) raise HTTPFound(location=url('repos')) try: _forks = repo.forks.count() handle_forks = None if _forks and request.POST.get('forks'): do = request.POST['forks'] if do == 'detach_forks': handle_forks = 'detach' h.flash(_('Detached %s forks') % _forks, category='success') elif do == 'delete_forks': handle_forks = 'delete' h.flash(_('Deleted %s forks') % _forks, category='success') repo_model.delete(repo, forks=handle_forks) action_logger(request.authuser, 'admin_deleted_repo', repo_name, request.ip_addr) ScmModel().mark_for_invalidation(repo_name) h.flash(_('Deleted repository %s') % repo_name, category='success') Session().commit() except AttachedForksError: h.flash(_('Cannot delete repository %s which still has forks') % repo_name, category='warning') except Exception: log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of %s') % repo_name, category='error') if repo.group: raise HTTPFound(location=url('repos_group_home', group_name=repo.group.group_name)) raise HTTPFound(location=url('repos'))
def repo_check(self, repo_name): c.repo = repo_name task_id = request.GET.get('task_id') if task_id and task_id not in ['None']: from kallithea import CELERY_ON from kallithea.lib import celerypylons if CELERY_ON: task = celerypylons.result.AsyncResult(task_id) if task.failed(): raise HTTPInternalServerError(task.traceback) repo = Repository.get_by_repo_name(repo_name) if repo and repo.repo_state == Repository.STATE_CREATED: if repo.clone_uri: h.flash(_('Created repository %s from %s') % (repo.repo_name, repo.clone_uri_hidden), category='success') else: repo_url = h.link_to(repo.repo_name, h.url('summary_home', repo_name=repo.repo_name)) fork = repo.fork if fork is not None: fork_name = fork.repo_name h.flash(h.literal(_('Forked repository %s as %s') % (fork_name, repo_url)), category='success') else: h.flash(h.literal(_('Created repository %s') % repo_url), category='success') return {'result': True} return {'result': False}
def update(self, id): _form = DefaultsForm()() try: form_result = _form.to_python(dict(request.POST)) for k, v in form_result.iteritems(): setting = Setting.create_or_update(k, v) Session().commit() h.flash(_('Default settings updated successfully'), category='success') except formencode.Invalid as errors: defaults = errors.value return htmlfill.render( render('admin/defaults/defaults.html'), defaults=defaults, errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8", force_defaults=False) except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during update of defaults'), category='error') raise HTTPFound(location=url('defaults'))
def create(self): self.__load_defaults() # permissions for can create group based on parent_id are checked # here in the Form repo_group_form = RepoGroupForm(repo_groups=c.repo_groups) try: form_result = repo_group_form.to_python(dict(request.POST)) gr = RepoGroupModel().create( group_name=form_result['group_name'], group_description=form_result['group_description'], parent=form_result['parent_group_id'], owner=request.authuser.user_id, # TODO: make editable copy_permissions=form_result['group_copy_permissions'] ) Session().commit() #TODO: in future action_logger(, '', '', '') except formencode.Invalid as errors: return htmlfill.render( render('admin/repo_groups/repo_group_add.html'), defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8", force_defaults=False) except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during creation of repository group %s') \ % request.POST.get('group_name'), category='error') parent_group_id = form_result['parent_group_id'] #TODO: maybe we should get back to the main view, not the admin one raise HTTPFound(location=url('repos_groups', parent_group=parent_group_id)) h.flash(_('Created repository group %s') % gr.group_name, category='success') raise HTTPFound(location=url('repos_group_home', group_name=gr.group_name))
def register(self, **kw): """ User register """ log.debug("Recibiendo de registro %s", kw) if request.identity: redirect('/') """ Adding new user """ user = model.User() user.email_address = kw.get('email', None) user.password = kw.get('password', None) user.user_name = kw.get('username', None) model.DBSession.add(user) # Persisting from sqlalchemy.exc import IntegrityError try: model.DBSession.flush() forceUser(user.user_name) flash(_(u'Bienvenido %s, su cuenta ha sido creada con éxito' % user.user_name), 'alert alert-success') redirect('/') except IntegrityError as e: log.debug('Error:', e) model.DBSession.rollback() if e.orig[0] == 1062: # User register already exits flash(_('Parece que ya tienes cuenta')) redirect('/login') else: raise
def put(self, item_id, label='',content=''): # TODO - SECURE THIS workspace = tmpl_context.workspace try: api = ContentApi(tmpl_context.current_user) item = api.get_one(int(item_id), self._item_type, workspace) with new_revision(item): api.update_content(item, label, content) if not self._path_validation.validate_new_content(item): return render_invalid_integrity_chosen_path( item.get_label(), ) api.save(item, ActionDescription.REVISION) msg = _('{} updated').format(self._item_type_label) tg.flash(msg, CST.STATUS_OK) tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item.content_id)) except SameValueError as e: msg = _('{} not updated: the content did not change').format(self._item_type_label) tg.flash(msg, CST.STATUS_WARNING) tg.redirect(self._err_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item_id)) except ValueError as e: msg = _('{} not updated - error: {}').format(self._item_type_label, str(e)) tg.flash(msg, CST.STATUS_ERROR) tg.redirect(self._err_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item_id))
def reset_request(self, **kw): user = model.provider.query(app_model.User, filters=dict(email_address=kw['email_address']))[1][0] password_frag = user.password[0:4] serializer = URLSafeSerializer(tg.config['beaker.session.secret']) serialized_data = serializer.dumps(dict(request_date=datetime.utcnow().strftime('%m/%d/%Y %H:%M'), email_address=kw['email_address'], password_frag=password_frag)) password_reset_link = tg.url(self.mount_point + "/change_password/", params=dict(data=serialized_data), qualified=True) reset_password_config = tg.config.get('_pluggable_resetpassword_config') mail_body = reset_password_config.get('mail_body', _(''' We've received a request to reset the password for this account. Please click this link to reset your password: %(password_reset_link)s If you no longer wish to make the above change, or if you did not initiate this request, please disregard and/or delete this e-mail. ''')) email_data = {'sender': tg.config['resetpassword.email_sender'], 'subject': reset_password_config.get('mail_subject', _('Password reset request')), 'body': mail_body, 'rich': reset_password_config.get('mail_rich', '')} tg.hooks.notify('resetpassword.on_request', args=(user, email_data, password_reset_link)) email_data['body'] = email_data['body'] % dict(password_reset_link=password_reset_link) email_data['rich'] = email_data['rich'] % dict(password_reset_link=password_reset_link) send_email(user.email_address, **email_data) flash(_('Password reset request sent')) return plug_redirect('resetpassword', '/')
def put(self, folder_id, label, can_contain_folders=False, can_contain_threads=False, can_contain_files=False, can_contain_pages=False): # TODO - SECURE THIS workspace = tmpl_context.workspace api = ContentApi(tmpl_context.current_user) next_url = '' try: folder = api.get_one(int(folder_id), ContentType.Folder, workspace) subcontent = dict( folder = True if can_contain_folders=='on' else False, thread = True if can_contain_threads=='on' else False, file = True if can_contain_files=='on' else False, page = True if can_contain_pages=='on' else False ) if label != folder.label: # TODO - D.A. - 2015-05-25 - Allow to set folder description api.update_content(folder, label, folder.description) api.set_allowed_content(folder, subcontent) api.save(folder) tg.flash(_('Folder updated'), CST.STATUS_OK) next_url = self.url(folder.content_id) except Exception as e: tg.flash(_('Folder not updated: {}').format(str(e)), CST.STATUS_ERROR) next_url = self.url(int(folder_id)) tg.redirect(next_url)
def put_delete(self, item_id): require_current_user_is_owner(int(item_id)) # TODO - CHECK RIGHTS item_id = int(item_id) content_api = ContentApi(tmpl_context.current_user) item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace) try: next_url = tg.url('/workspaces/{}/folders/{}/threads/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, tmpl_context.thread_id) undo_url = tg.url('/workspaces/{}/folders/{}/threads/{}/comments/{}/put_delete_undo').format(tmpl_context.workspace_id, tmpl_context.folder_id, tmpl_context.thread_id, item_id) msg = _('{} deleted. <a class="alert-link" href="{}">Cancel action</a>').format(self._item_type_label, undo_url) content_api.delete(item) content_api.save(item, ActionDescription.DELETION) tg.flash(msg, CST.STATUS_OK, no_escape=True) tg.redirect(next_url) except ValueError as e: back_url = tg.url('/workspaces/{}/folders/{}/threads/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, tmpl_context.thread_id) msg = _('{} not deleted: {}').format(self._item_type_label, str(e)) tg.flash(msg, CST.STATUS_ERROR) tg.redirect(back_url)
def __get_cs(self, rev, silent_empty=False): """ Safe way to get changeset if error occur it redirects to tip with proper message :param rev: revision to fetch :silent_empty: return None if repository is empty """ try: return c.db_repo_scm_instance.get_changeset(rev) except EmptyRepositoryError as e: if silent_empty: return None url_ = url('files_add_home', repo_name=c.repo_name, revision=0, f_path='', anchor='edit') add_new = h.link_to(_('Click here to add new file'), url_, class_="alert-link") h.flash(h.literal(_('There are no files yet. %s') % add_new), category='warning') raise HTTPNotFound() except (ChangesetDoesNotExistError, LookupError): msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') raise HTTPNotFound() except RepositoryError as e: h.flash(safe_str(e), category='error') raise HTTPNotFound()
def _ignorews_url(GET, fileid=None): fileid = str(fileid) if fileid else None params = defaultdict(list) _update_with_GET(params, GET) lbl = _('Show whitespace') ig_ws = get_ignore_ws(fileid, GET) ln_ctx = get_line_ctx(fileid, GET) # global option if fileid is None: if ig_ws is None: params['ignorews'] += [1] lbl = _('Ignore whitespace') ctx_key = 'context' ctx_val = ln_ctx # per file options else: if ig_ws is None: params[fileid] += ['WS:1'] lbl = _('Ignore whitespace') ctx_key = fileid ctx_val = 'C:%s' % ln_ctx # if we have passed in ln_ctx pass it along to our params if ln_ctx: params[ctx_key] += [ctx_val] params['anchor'] = fileid icon = h.literal('<i class="icon-strike"></i>') return h.link_to(icon, h.url.current(**params), title=lbl, **{'data-toggle': 'tooltip'})
def delete(self, group_name): gr = c.repo_group = RepoGroup.guess_instance(group_name) repos = gr.repositories.all() if repos: h.flash(_('This group contains %s repositories and cannot be ' 'deleted') % len(repos), category='warning') raise HTTPFound(location=url('repos_groups')) children = gr.children.all() if children: h.flash(_('This group contains %s subgroups and cannot be deleted' % (len(children))), category='warning') raise HTTPFound(location=url('repos_groups')) try: RepoGroupModel().delete(group_name) Session().commit() h.flash(_('Removed repository group %s') % group_name, category='success') #TODO: in future action_logger(, '', '', '') except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during deletion of repository group %s') % group_name, category='error') if gr.parent_group: raise HTTPFound(location=url('repos_group_home', group_name=gr.parent_group.group_name)) raise HTTPFound(location=url('repos_groups'))
def validate_python(self, value, state): super(UniqueUserValidator, self).validate_python(value, state) if re.match("^[a-zA-Z0-9_-]*[a-zA-Z_-][a-zA-Z0-9_-]*$", value): reg = DBSession.query(User).filter_by(user_name=value).first() user = DBSession.query(User).filter_by(user_name=value).first() if reg or user: raise Invalid(_('Username already in use.'), value, state) else: raise Invalid(_('Invalid username'), value, state)
def delete(self, repo_name, revision, f_path): repo = c.db_repo if repo.enable_locking and repo.locked[0]: h.flash(_('This repository has been locked by %s on %s') % (h.person_by_id(repo.locked[0]), h.fmt_date(h.time_to_datetime(repo.locked[1]))), 'warning') raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip')) # check if revision is a branch identifier- basically we cannot # create multiple heads via file editing _branches = repo.scm_instance.branches # check if revision is a branch name or branch hash if revision not in _branches.keys() + _branches.values(): h.flash(_('You can only delete files with revision ' 'being a valid branch'), category='warning') raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip', f_path=f_path)) r_post = request.POST c.cs = self.__get_cs(revision) c.file = self.__get_filenode(c.cs, f_path) c.default_message = _('Deleted file %s via Kallithea') % (f_path) c.f_path = f_path node_path = f_path author = request.authuser.full_contact if r_post: message = r_post.get('message') or c.default_message try: nodes = { node_path: { 'content': '' } } self.scm_model.delete_nodes( user=request.authuser.user_id, repo=c.db_repo, message=message, nodes=nodes, parent_cs=c.cs, author=author, ) h.flash(_('Successfully deleted file %s') % f_path, category='success') except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during commit'), category='error') raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) return render('files/files_delete.html')
def validate_python(self, value, state): super(UniqueEmailValidator, self).validate_python(value, state) if re.match("^(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6}$", value): reg = DBSession.query(User).filter_by(email_address=value).first() user = DBSession.query(User).filter_by(email_address=value).first() if reg or user: raise Invalid(_('Email address has already been taken'), value, state) else: raise Invalid(_('Invalid email'), value, state)
def post( self, name: str, email: str, password: str, is_tracim_manager: str='off', is_tracim_admin: str='off', send_email: str='off', ): is_tracim_manager = h.on_off_to_boolean(is_tracim_manager) is_tracim_admin = h.on_off_to_boolean(is_tracim_admin) send_email = h.on_off_to_boolean(send_email) current_user = tmpl_context.current_user if current_user.profile.id < Group.TIM_ADMIN: # A manager can't give large rights is_tracim_manager = False is_tracim_admin = False api = UserApi(current_user) if api.user_with_email_exists(email): tg.flash(_('A user with email address "{}" already exists.').format(email), CST.STATUS_ERROR) tg.redirect(self.url()) user = api.create_user() user.email = email user.display_name = name if password: user.password = password elif send_email: # Setup a random password to send email at user password = str(uuid.uuid4()) user.password = password user.webdav_left_digest_response_hash = '%s:/:%s' % (email, password) api.save(user) # Now add the user to related groups group_api = GroupApi(current_user) user.groups.append(group_api.get_one(Group.TIM_USER)) if is_tracim_manager: user.groups.append(group_api.get_one(Group.TIM_MANAGER)) if is_tracim_admin: user.groups.append(group_api.get_one(Group.TIM_ADMIN)) api.save(user) if send_email: email_manager = get_email_manager() email_manager.notify_created_account(user, password=password) tg.flash(_('User {} created.').format(user.get_display_name()), CST.STATUS_OK) tg.redirect(self.url())
def allocations(self, *args, **kwargs): html = self.get_active_allocations_html(*args, **kwargs) javascript = self.get_javascript_allocations_onload() title = _("Allocations") return dict(title=title, html=html, javascript=javascript)
def index(self, repo_name, revision, f_path, annotate=False): # redirect to given revision from form if given post_revision = request.POST.get('at_rev', None) if post_revision: cs = self.__get_cs(post_revision) # FIXME - unused! c.revision = revision c.changeset = self.__get_cs(revision) c.branch = request.GET.get('branch', None) c.f_path = f_path c.annotate = annotate cur_rev = c.changeset.revision # used in files_source.html: c.cut_off_limit = self.cut_off_limit c.fulldiff = request.GET.get('fulldiff') # prev link try: prev_rev = c.db_repo_scm_instance.get_changeset(cur_rev).prev( c.branch) c.url_prev = url('files_home', repo_name=c.repo_name, revision=prev_rev.raw_id, f_path=f_path) if c.branch: c.url_prev += '?branch=%s' % c.branch except (ChangesetDoesNotExistError, VCSError): c.url_prev = '#' # next link try: next_rev = c.db_repo_scm_instance.get_changeset(cur_rev).next( c.branch) c.url_next = url('files_home', repo_name=c.repo_name, revision=next_rev.raw_id, f_path=f_path) if c.branch: c.url_next += '?branch=%s' % c.branch except (ChangesetDoesNotExistError, VCSError): c.url_next = '#' # files or dirs try: c.file = c.changeset.get_node(f_path) if c.file.is_submodule(): raise HTTPFound(location=c.file.url) elif c.file.is_file(): c.load_full_history = False # determine if we're on branch head _branches = c.db_repo_scm_instance.branches c.on_branch_head = revision in _branches or revision in _branches.values( ) _hist = [] c.file_history = [] if c.load_full_history: c.file_history, _hist = self._get_node_history( c.changeset, f_path) c.authors = [] for a in set([x.author for x in _hist]): c.authors.append((h.email(a), h.person(a))) else: c.authors = c.file_history = [] except RepositoryError as e: h.flash(e, category='error') raise HTTPNotFound() if request.environ.get('HTTP_X_PARTIAL_XHR'): return render('files/files_ypjax.html') # TODO: tags and bookmarks? c.revision_options = [(c.changeset.raw_id, _('%s at %s') % (b, h.short_id(c.changeset.raw_id))) for b in c.changeset.branches] + \ [(n, b) for b, n in c.db_repo_scm_instance.branches.items()] if c.db_repo_scm_instance.closed_branches: prefix = _('(closed)') + ' ' c.revision_options += [('-', '-')] + \ [(n, prefix + b) for b, n in c.db_repo_scm_instance.closed_branches.items()] return render('files/files.html')
def title(self): return _('Request')
def _build_email_body(self, mako_template_filepath: str, role: UserRoleInWorkspace, content: Content, actor: User) -> str: """ Build an email body and return it as a string :param mako_template_filepath: the absolute path to the mako template to be used for email body building :param role: the role related to user to whom the email must be sent. The role is required (and not the user only) in order to show in the mail why the user receive the notification :param content: the content item related to the notification :param actor: the user at the origin of the action / notification (for example the one who wrote a comment :param config: the global configuration :return: the built email body as string. In case of multipart email, this method must be called one time for text and one time for html """ logger.debug( self, 'Building email content from MAKO template {}'.format( mako_template_filepath)) template = Template(filename=mako_template_filepath) # TODO - D.A. - 2014-11-06 - move this # Import is here for circular import problem import tracim.lib.helpers as helpers dictified_item = Context( CTX.EMAIL_NOTIFICATION, self._global_config.WEBSITE_BASE_URL).toDict(content) dictified_actor = Context(CTX.DEFAULT).toDict(actor) main_title = dictified_item.label content_intro = '' content_text = '' call_to_action_text = '' action = content.get_last_action().id if ActionDescription.COMMENT == action: content_intro = _( '<span id="content-intro-username">{}</span> added a comment:' ).format(actor.display_name) content_text = content.description call_to_action_text = _('Answer') elif ActionDescription.CREATION == action: # Default values (if not overriden) content_text = content.description call_to_action_text = _('View online') if ContentType.Thread == content.type: call_to_action_text = _('Answer') content_intro = _( '<span id="content-intro-username">{}</span> started a thread entitled:' ).format(actor.display_name) content_text = '<p id="content-body-intro">{}</p>'.format(content.label) + \ content.get_last_comment_from(actor).description elif ContentType.File == content.type: content_intro = _( '<span id="content-intro-username">{}</span> added a file entitled:' ).format(actor.display_name) if content.description: content_text = content.description elif content.label: content_text = '<span id="content-body-only-title">{}</span>'.format( content.label) else: content_text = '<span id="content-body-only-title">{}</span>'.format( content.file_name) elif ContentType.Page == content.type: content_intro = _( '<span id="content-intro-username">{}</span> added a page entitled:' ).format(actor.display_name) content_text = '<span id="content-body-only-title">{}</span>'.format( content.label) elif ActionDescription.REVISION == action: content_text = content.description call_to_action_text = _('View online') if ContentType.File == content.type: content_intro = _( '<span id="content-intro-username">{}</span> uploaded a new revision.' ).format(actor.display_name) content_text = '' elif ContentType.Page == content.type: content_intro = _( '<span id="content-intro-username">{}</span> updated this page.' ).format(actor.display_name) previous_revision = content.get_previous_revision() title_diff = '' if previous_revision.label != content.label: title_diff = htmldiff(previous_revision.label, content.label) content_text = _('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \ title_diff + \ htmldiff(previous_revision.description, content.description) elif ContentType.Thread == content.type: content_intro = _( '<span id="content-intro-username">{}</span> updated the thread description.' ).format(actor.display_name) previous_revision = content.get_previous_revision() title_diff = '' if previous_revision.label != content.label: title_diff = htmldiff(previous_revision.label, content.label) content_text = _('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \ title_diff + \ htmldiff(previous_revision.description, content.description) # elif ContentType.Thread == content.type: # content_intro = _('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name) # previous_revision = content.get_previous_revision() # content_text = _('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \ # htmldiff(previous_revision.description, content.description) elif ActionDescription.EDITION == action: call_to_action_text = _('View online') if ContentType.File == content.type: content_intro = _( '<span id="content-intro-username">{}</span> updated the file description.' ).format(actor.display_name) content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \ content.description if '' == content_intro and content_text == '': # Skip notification, but it's not normal logger.error( self, 'A notification is being sent but no content. ' 'Here are some debug informations: [content_id: {cid}]' '[action: {act}][author: {actor}]'.format( cid=content.content_id, act=action, actor=actor)) raise ValueError('Unexpected empty notification') # Import done here because cyclic import from tracim.config.app_cfg import CFG body_content = template.render( base_url=self._global_config.WEBSITE_BASE_URL, _=_, h=helpers, user_display_name=role.user.display_name, user_role_label=role.role_as_label(), workspace_label=role.workspace.label, content_intro=content_intro, content_text=content_text, main_title=main_title, call_to_action_text=call_to_action_text, result=DictLikeClass(item=dictified_item, actor=dictified_actor), CFG=CFG.get_instance(), ) return body_content
def add(self, repo_name, revision, f_path): repo = c.db_repo r_post = request.POST c.cs = self.__get_cs(revision, silent_empty=True) if c.cs is None: c.cs = EmptyChangeset(alias=c.db_repo_scm_instance.alias) c.default_message = (_('Added file via Kallithea')) c.f_path = f_path if r_post: unix_mode = 0 content = convert_line_endings(r_post.get('content', ''), unix_mode) message = r_post.get('message') or c.default_message filename = r_post.get('filename') location = r_post.get('location', '') file_obj = r_post.get('upload_file', None) if file_obj is not None and hasattr(file_obj, 'filename'): filename = file_obj.filename content = file_obj.file if hasattr(content, 'file'): # non posix systems store real file under file attr content = content.file if not content: h.flash(_('No content'), category='warning') raise HTTPFound(location=url( 'changeset_home', repo_name=c.repo_name, revision='tip')) if not filename: h.flash(_('No filename'), category='warning') raise HTTPFound(location=url( 'changeset_home', repo_name=c.repo_name, revision='tip')) # strip all crap out of file, just leave the basename filename = os.path.basename(filename) node_path = posixpath.join(location, filename) author = request.authuser.full_contact try: nodes = {node_path: {'content': content}} self.scm_model.create_nodes( user=request.authuser.user_id, ip_addr=request.ip_addr, repo=c.db_repo, message=message, nodes=nodes, parent_cs=c.cs, author=author, ) h.flash(_('Successfully committed to %s') % node_path, category='success') except NonRelativePathError as e: h.flash(_('Location must be relative path and must not ' 'contain .. in path'), category='warning') raise HTTPFound(location=url( 'changeset_home', repo_name=c.repo_name, revision='tip')) except (NodeError, NodeAlreadyExistsError) as e: h.flash(_(e), category='error') except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during commit'), category='error') raise HTTPFound(location=url( 'changeset_home', repo_name=c.repo_name, revision='tip')) return render('files/files_add.html')
def save(self, **kw): kw.pop('nothing') flash_message = _('Profile successfully updated') user = request.identity['user'] # validate the form try: form = create_user_form(user) kw = form.validate(kw) except ValidationError: override_template(self.save, 'kajiki:userprofile.templates.edit') user_data, user_avatar = get_user_data(user) if sys.version_info >= (3, 3): from types import SimpleNamespace else: from argparse import Namespace as SimpleNamespace u = SimpleNamespace() for k, v in kw.items(): u.__setattr__(k, v) return dict(user=u, profile_css=get_profile_css(config), user_avatar=user_avatar, form=form) # get the profile_save function that may be custom profile_save = getattr(user, 'save_profile', None) if not profile_save: profile_save = update_user_data # we don't want to save the email until it is confirmed new_email = kw['email_address'] kw['email_address'] = user.email_address profile_save(user, kw) if new_email != user.email_address: # save this new email in the db dictionary = { 'old_email_address': user.email_address, 'email_address': new_email, 'activation_code': model.ProfileActivation.generate_activation_code(new_email), } activation = model.provider.create(model.ProfileActivation, dictionary) # ok, send the email please userprofile_config = config.get('_pluggable_userprofile_config') mail_body = userprofile_config.get( 'mail_body', _('Please click on this link to confermate your email address') + '\n\n' + activation.activation_link, ) email_data = {'sender': config['userprofile.email_sender'], 'subject': userprofile_config.get( 'mail_subject', _('Please confirm your email')), 'body': mail_body, 'rich': userprofile_config.get('mail_rich', '')} send_email(new_email, **email_data) flash_message += '.\n' + _('Confirm your email please') flash(flash_message) return redirect(plug_url('userprofile', '/'))
def _index(self, revision, method): c.pull_request = None c.anchor_url = anchor_url c.ignorews_url = _ignorews_url c.context_url = _context_url c.fulldiff = request.GET.get( 'fulldiff') # for reporting number of changed files # get ranges of revisions if preset rev_range = revision.split('...')[:2] enable_comments = True c.cs_repo = c.db_repo try: if len(rev_range) == 2: enable_comments = False rev_start = rev_range[0] rev_end = rev_range[1] rev_ranges = c.db_repo_scm_instance.get_changesets( start=rev_start, end=rev_end) else: rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)] c.cs_ranges = list(rev_ranges) if not c.cs_ranges: raise RepositoryError('Changeset range returned empty result') except (ChangesetDoesNotExistError, EmptyRepositoryError): log.debug(traceback.format_exc()) msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') raise HTTPNotFound() c.changes = OrderedDict() c.lines_added = 0 # count of lines added c.lines_deleted = 0 # count of lines removes c.changeset_statuses = ChangesetStatus.STATUSES comments = dict() c.statuses = [] c.inline_comments = [] c.inline_cnt = 0 # Iterate over ranges (default changeset view is always one changeset) for changeset in c.cs_ranges: if method == 'show': c.statuses.extend([ ChangesetStatusModel().get_status(c.db_repo.repo_id, changeset.raw_id) ]) # Changeset comments comments.update( (com.comment_id, com) for com in ChangesetCommentsModel().get_comments( c.db_repo.repo_id, revision=changeset.raw_id)) # Status change comments - mostly from pull requests comments.update(( st.comment_id, st.comment ) for st in ChangesetStatusModel().get_statuses( c.db_repo.repo_id, changeset.raw_id, with_revisions=True) if st.comment_id is not None) inlines = ChangesetCommentsModel() \ .get_inline_comments(c.db_repo.repo_id, revision=changeset.raw_id) c.inline_comments.extend(inlines) cs2 = changeset.raw_id cs1 = changeset.parents[ 0].raw_id if changeset.parents else EmptyChangeset().raw_id context_lcl = get_line_ctx('', request.GET) ign_whitespace_lcl = get_ignore_ws('', request.GET) raw_diff = diffs.get_diff(c.db_repo_scm_instance, cs1, cs2, ignore_whitespace=ign_whitespace_lcl, context=context_lcl) diff_limit = None if c.fulldiff else self.cut_off_limit file_diff_data = [] if method == 'show': diff_processor = diffs.DiffProcessor( raw_diff, vcs=c.db_repo_scm_instance.alias, diff_limit=diff_limit) c.limited_diff = diff_processor.limited_diff for f in diff_processor.parsed: st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] filename = f['filename'] fid = h.FID(changeset.raw_id, filename) url_fid = h.FID('', filename) html_diff = diffs.as_html(enable_comments=enable_comments, parsed_lines=[f]) file_diff_data.append( (fid, url_fid, f['operation'], f['old_filename'], filename, html_diff, st)) else: # downloads/raw we only need RAW diff nothing else file_diff_data.append(('', None, None, None, raw_diff, None)) c.changes[changeset.raw_id] = (cs1, cs2, file_diff_data) # sort comments in creation order c.comments = [com for com_id, com in sorted(comments.items())] # count inline comments for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) if len(c.cs_ranges) == 1: c.changeset = c.cs_ranges[0] c.parent_tmpl = ''.join( ['# Parent %s\n' % x.raw_id for x in c.changeset.parents]) c.changeset_graft_source_hash = ascii_str( c.changeset.extra.get(b'source', b'')) c.changeset_transplant_source_hash = ascii_str( binascii.hexlify( c.changeset.extra.get(b'transplant_source', b''))) if method == 'download': response.content_type = 'text/plain' response.content_disposition = 'attachment; filename=%s.diff' \ % revision[:12] return raw_diff elif method == 'patch': response.content_type = 'text/plain' c.diff = safe_str(raw_diff) return render('changeset/patch_changeset.html') elif method == 'raw': response.content_type = 'text/plain' return raw_diff elif method == 'show': if len(c.cs_ranges) == 1: return render('changeset/changeset.html') else: c.cs_ranges_org = None c.cs_comments = {} revs = [ctx.revision for ctx in reversed(c.cs_ranges)] c.jsdata = graph_data(c.db_repo_scm_instance, revs) return render('changeset/changeset_range.html')
def serialize_content_for_folder_content_list(content: Content, context: Context): content_type = ContentType(content.type) last_activity_date = content.get_last_activity_date() last_activity_date_formatted = format_datetime( last_activity_date, locale=tg.i18n.get_lang()[0]) last_activity_label = format_timedelta(datetime.utcnow() - last_activity_date, locale=tg.i18n.get_lang()[0]) last_activity_label = last_activity_label.replace( ' ', '\u00A0') # espace insécable item = None if ContentType.Thread == content.type: item = Context(CTX.THREADS).toDict(content) item.type = context.toDict(content_type) item.folder = DictLikeClass({'id': content.parent_id }) if content.parent else None item.workspace = DictLikeClass({'id': content.workspace.workspace_id }) if content.workspace else None item.last_activity = DictLikeClass({ 'date': last_activity_date, 'label': last_activity_date_formatted, 'delta': last_activity_label }) comments = content.get_comments() if len(comments) > 1: item.notes = _('{nb} messages').format(nb=len(comments)) else: item.notes = _('1 message') elif ContentType.File == content.type: item = Context(CTX.CONTENT_LIST).toDict(content) if len(content.revisions) > 1: item.notes = _('{nb} revisions').format(nb=len(content.revisions)) else: item.notes = _('1 revision') elif ContentType.Folder == content.type: item = Context(CTX.CONTENT_LIST).toDict(content) item.notes = '' folder_nb = content.get_child_nb(ContentType.Folder) if folder_nb == 1: item.notes += _('1 subfolder<br/>\n') elif folder_nb > 1: item.notes += _('{} subfolders<br/>').format(folder_nb) file_nb = content.get_child_nb(ContentType.File, ContentStatus.OPEN) if file_nb == 1: item.notes += _('1 open file<br/>\n') elif file_nb > 1: item.notes += _('{} open files<br/>').format(file_nb) thread_nb = content.get_child_nb(ContentType.Thread, ContentStatus.OPEN) if thread_nb == 1: item.notes += _('1 open thread<br/>\n') elif thread_nb > 1: item.notes += _('{} open threads<br/>').format(thread_nb) page_nb = content.get_child_nb(ContentType.Page, ContentStatus.OPEN) if page_nb == 1: item.notes += _('1 open page<br/>\n') elif page_nb > 1: item.notes += _('{} open pages<br/>').format(page_nb) else: item = Context(CTX.CONTENT_LIST).toDict(content) item.notes = '' return item
def policy_sales(self, *args, **kwargs): html = self.get_active_policy_sales_html(*args, **kwargs) javascript = self.get_javascript_policy_sales_onload() title = _("Policy Sales") return dict(title=title, html=html, javascript=javascript)
def post_logout(self, came_from=lurl('/')): """ Redirect the user to the initially requested page on logout and say goodbye as well. """ flash(_('Successfully logged out. We hope to see you soon!')) redirect(came_from)
def put(self, new_profile): # FIXME - Allow only self password or operation for managers current_user = tmpl_context.current_user user = tmpl_context.user group_api = GroupApi(current_user) if current_user.user_id == user.user_id: tg.flash(_('You can\'t change your own profile'), CST.STATUS_ERROR) tg.redirect(self.parent_controller.url()) redirect_url = self.parent_controller.url(skip_id=True) if new_profile not in self.allowed_profiles: tg.flash(_('Unknown profile'), CST.STATUS_ERROR) tg.redirect(redirect_url) pod_user_group = group_api.get_one(Group.TIM_USER) pod_manager_group = group_api.get_one(Group.TIM_MANAGER) pod_admin_group = group_api.get_one(Group.TIM_ADMIN) flash_message = _( 'User updated.') # this is the default value ; should never appear if new_profile == UserProfileAdminRestController._ALLOWED_PROFILE_USER: if pod_user_group not in user.groups: user.groups.append(pod_user_group) try: user.groups.remove(pod_manager_group) except: pass try: user.groups.remove(pod_admin_group) except: pass flash_message = _('User {} is now a basic user').format( user.get_display_name()) elif new_profile == UserProfileAdminRestController._ALLOWED_PROFILE_MANAGER: if pod_user_group not in user.groups: user.groups.append(pod_user_group) if pod_manager_group not in user.groups: user.groups.append(pod_manager_group) try: user.groups.remove(pod_admin_group) except: pass flash_message = _('User {} can now workspaces').format( user.get_display_name()) elif new_profile == UserProfileAdminRestController._ALLOWED_PROFILE_ADMIN: if pod_user_group not in user.groups: user.groups.append(pod_user_group) if pod_manager_group not in user.groups: user.groups.append(pod_manager_group) if pod_admin_group not in user.groups: user.groups.append(pod_admin_group) flash_message = _('User {} is now an administrator').format( user.get_display_name()) else: logger.error( self, 'Trying to change user {} profile with unexpected profile {}'. format(user.user_id, new_profile)) tg.flash(_('Unknown profile'), CST.STATUS_ERROR) tg.redirect(redirect_url) DBSession.flush() tg.flash(flash_message, CST.STATUS_OK) tg.redirect(redirect_url)
def glaccounts(self, *args, **kwargs): html = self.get_active_glaccounts_html(*args, **kwargs) javascript = self.get_javascript_glaccounts_onload() title = _("GL Account Extract") return dict(title=title, html=html, javascript=javascript)
def add_reviewers(self, user, pr, reviewers, mention_recipients=None): """Add reviewer and send notification to them. """ reviewers = set(reviewers) _assert_valid_reviewers(reviewers) if mention_recipients is not None: mention_recipients = set(mention_recipients) - reviewers _assert_valid_reviewers(mention_recipients) # members for reviewer in reviewers: prr = PullRequestReviewer(reviewer, pr) Session().add(prr) # notification to reviewers pr_url = pr.url(canonical=True) threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name, pr.pull_request_id, h.canonical_hostname())] subject = h.link_to( _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') % {'user': user.username, 'pr_title': pr.title, 'pr_nice_id': pr.nice_id()}, pr_url) body = pr.description _org_ref_type, org_ref_name, _org_rev = pr.org_ref.split(':') _other_ref_type, other_ref_name, _other_rev = pr.other_ref.split(':') revision_data = [(x.raw_id, x.message) for x in map(pr.org_repo.get_changeset, pr.revisions)] email_kwargs = { 'pr_title': pr.title, 'pr_title_short': h.shorter(pr.title, 50), 'pr_user_created': user.full_name_and_username, 'pr_repo_url': h.canonical_url('summary_home', repo_name=pr.other_repo.repo_name), 'pr_url': pr_url, 'pr_revisions': revision_data, 'repo_name': pr.other_repo.repo_name, 'org_repo_name': pr.org_repo.repo_name, 'pr_nice_id': pr.nice_id(), 'pr_target_repo': h.canonical_url('summary_home', repo_name=pr.other_repo.repo_name), 'pr_target_branch': other_ref_name, 'pr_source_repo': h.canonical_url('summary_home', repo_name=pr.org_repo.repo_name), 'pr_source_branch': org_ref_name, 'pr_owner': pr.owner, 'pr_owner_username': pr.owner.username, 'pr_username': user.username, 'threading': threading, 'is_mention': False, } if reviewers: NotificationModel().create(created_by=user, subject=subject, body=body, recipients=reviewers, type_=NotificationModel.TYPE_PULL_REQUEST, email_kwargs=email_kwargs) if mention_recipients: email_kwargs['is_mention'] = True subject = _('[Mention]') + ' ' + subject # FIXME: this subject is wrong and unused! NotificationModel().create(created_by=user, subject=subject, body=body, recipients=mention_recipients, type_=NotificationModel.TYPE_PULL_REQUEST, email_kwargs=email_kwargs)
def not_mapped_error(repo_name): flash(_('%s repository is not mapped to db perhaps' ' it was created or renamed from the filesystem' ' please run the application again' ' in order to rescan repositories') % repo_name, category='error')
def index(self): """Let the user know that's visiting a protected controller.""" flash(_("Secure Controller here")) return dict(page='index')
def action_parser(user_log, feed=False, parse_cs=False): """ This helper will action_map the specified string action into translated fancy names with icons and links :param user_log: user log instance :param feed: use output for feeds (no html and fancy icons) :param parse_cs: parse Changesets into VCS instances """ action = user_log.action action_params = ' ' x = action.split(':') if len(x) > 1: action, action_params = x def get_cs_links(): revs_limit = 3 # display this amount always revs_top_limit = 50 # show upto this amount of changesets hidden revs_ids = action_params.split(',') deleted = user_log.repository is None if deleted: return ','.join(revs_ids) repo_name = user_log.repository.repo_name def lnk(rev, repo_name): lazy_cs = False title_ = None url_ = '#' if isinstance(rev, BaseChangeset) or isinstance( rev, AttributeDict): if rev.op and rev.ref_name: if rev.op == 'delete_branch': lbl = _('Deleted branch: %s') % rev.ref_name elif rev.op == 'tag': lbl = _('Created tag: %s') % rev.ref_name else: lbl = 'Unknown operation %s' % rev.op else: lazy_cs = True lbl = rev.short_id[:8] url_ = url('changeset_home', repo_name=repo_name, revision=rev.raw_id) else: # changeset cannot be found - it might have been stripped or removed lbl = rev[:12] title_ = _('Changeset %s not found') % lbl if parse_cs: return link_to(lbl, url_, title=title_, **{'data-toggle': 'tooltip'}) return link_to(lbl, url_, class_='lazy-cs' if lazy_cs else '', **{ 'data-raw_id': rev.raw_id, 'data-repo_name': repo_name }) def _get_op(rev_txt): _op = None _name = rev_txt if len(rev_txt.split('=>')) == 2: _op, _name = rev_txt.split('=>') return _op, _name revs = [] if len(filter(lambda v: v != '', revs_ids)) > 0: repo = None for rev in revs_ids[:revs_top_limit]: _op, _name = _get_op(rev) # we want parsed changesets, or new log store format is bad if parse_cs: try: if repo is None: repo = user_log.repository.scm_instance _rev = repo.get_changeset(rev) revs.append(_rev) except ChangesetDoesNotExistError: log.error('cannot find revision %s in this repo', rev) revs.append(rev) else: _rev = AttributeDict({ 'short_id': rev[:12], 'raw_id': rev, 'message': '', 'op': _op, 'ref_name': _name }) revs.append(_rev) cs_links = [ " " + ', '.join([lnk(rev, repo_name) for rev in revs[:revs_limit]]) ] _op1, _name1 = _get_op(revs_ids[0]) _op2, _name2 = _get_op(revs_ids[-1]) _rev = '%s...%s' % (_name1, _name2) compare_view = ( ' <div class="compare_view" data-toggle="tooltip" title="%s">' '<a href="%s">%s</a> </div>' % (_('Show all combined changesets %s->%s') % (revs_ids[0][:12], revs_ids[-1][:12]), url('changeset_home', repo_name=repo_name, revision=_rev), _('Compare view'))) # if we have exactly one more than normally displayed # just display it, takes less space than displaying # "and 1 more revisions" if len(revs_ids) == revs_limit + 1: cs_links.append(", " + lnk(revs[revs_limit], repo_name)) # hidden-by-default ones if len(revs_ids) > revs_limit + 1: uniq_id = revs_ids[0] html_tmpl = ('<span> %s <a class="show_more" id="_%s" ' 'href="#more">%s</a> %s</span>') if not feed: cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') % (len(revs_ids) - revs_limit), _('revisions'))) if not feed: html_tmpl = '<span id="%s" style="display:none">, %s </span>' else: html_tmpl = '<span id="%s"> %s </span>' morelinks = ', '.join( [lnk(rev, repo_name) for rev in revs[revs_limit:]]) if len(revs_ids) > revs_top_limit: morelinks += ', ...' cs_links.append(html_tmpl % (uniq_id, morelinks)) if len(revs) > 1: cs_links.append(compare_view) return ''.join(cs_links) def get_fork_name(): repo_name = action_params url_ = url('summary_home', repo_name=repo_name) return _('Fork name %s') % link_to(action_params, url_) def get_user_name(): user_name = action_params return user_name def get_users_group(): group_name = action_params return group_name def get_pull_request(): from kallithea.model.db import PullRequest pull_request_id = action_params nice_id = PullRequest.make_nice_id(pull_request_id) deleted = user_log.repository is None if deleted: repo_name = user_log.repository_name else: repo_name = user_log.repository.repo_name return link_to( _('Pull request %s') % nice_id, url('pullrequest_show', repo_name=repo_name, pull_request_id=pull_request_id)) def get_archive_name(): archive_name = action_params return archive_name # action : translated str, callback(extractor), icon action_map = { 'user_deleted_repo': (_('[deleted] repository'), None, 'icon-trashcan'), 'user_created_repo': (_('[created] repository'), None, 'icon-plus'), 'user_created_fork': (_('[created] repository as fork'), None, 'icon-fork'), 'user_forked_repo': (_('[forked] repository'), get_fork_name, 'icon-fork'), 'user_updated_repo': (_('[updated] repository'), None, 'icon-pencil'), 'user_downloaded_archive': (_('[downloaded] archive from repository'), get_archive_name, 'icon-download-cloud'), 'admin_deleted_repo': (_('[delete] repository'), None, 'icon-trashcan'), 'admin_created_repo': (_('[created] repository'), None, 'icon-plus'), 'admin_forked_repo': (_('[forked] repository'), None, 'icon-fork'), 'admin_updated_repo': (_('[updated] repository'), None, 'icon-pencil'), 'admin_created_user': (_('[created] user'), get_user_name, 'icon-user'), 'admin_updated_user': (_('[updated] user'), get_user_name, 'icon-user'), 'admin_created_users_group': (_('[created] user group'), get_users_group, 'icon-pencil'), 'admin_updated_users_group': (_('[updated] user group'), get_users_group, 'icon-pencil'), 'user_commented_revision': (_('[commented] on revision in repository'), get_cs_links, 'icon-comment'), 'user_commented_pull_request': (_('[commented] on pull request for'), get_pull_request, 'icon-comment'), 'user_closed_pull_request': (_('[closed] pull request for'), get_pull_request, 'icon-ok'), 'push': (_('[pushed] into'), get_cs_links, 'icon-move-up'), 'push_local': (_('[committed via Kallithea] into repository'), get_cs_links, 'icon-pencil'), 'push_remote': (_('[pulled from remote] into repository'), get_cs_links, 'icon-move-up'), 'pull': (_('[pulled] from'), None, 'icon-move-down'), 'started_following_repo': (_('[started following] repository'), None, 'icon-heart'), 'stopped_following_repo': (_('[stopped following] repository'), None, 'icon-heart-empty'), } action_str = action_map.get(action, action) if feed: action = action_str[0].replace('[', '').replace(']', '') else: action = action_str[0] \ .replace('[', '<b>') \ .replace(']', '</b>') action_params_func = lambda: "" if callable(action_str[1]): action_params_func = action_str[1] def action_parser_icon(): action = user_log.action action_params = None x = action.split(':') if len(x) > 1: action, action_params = x ico = action_map.get(action, ['', '', ''])[2] html = """<i class="%s"></i>""" % ico return literal(html) # returned callbacks we need to call to get return [lambda: literal(action), action_params_func, action_parser_icon]
def archivefile(self, repo_name, fname): fileformat = None revision = None ext = None subrepos = request.GET.get('subrepos') == 'true' for a_type, ext_data in settings.ARCHIVE_SPECS.items(): archive_spec = fname.split(ext_data[1]) if len(archive_spec) == 2 and archive_spec[1] == '': fileformat = a_type or ext_data[1] revision = archive_spec[0] ext = ext_data[1] try: dbrepo = RepoModel().get_by_repo_name(repo_name) if not dbrepo.enable_downloads: return _('Downloads disabled') # TODO: do something else? if c.db_repo_scm_instance.alias == 'hg': # patch and reset hooks section of UI config to not run any # hooks on fetching archives with subrepos for k, v in c.db_repo_scm_instance._repo.ui.configitems( 'hooks'): c.db_repo_scm_instance._repo.ui.setconfig('hooks', k, None) cs = c.db_repo_scm_instance.get_changeset(revision) content_type = settings.ARCHIVE_SPECS[fileformat][0] except ChangesetDoesNotExistError: return _('Unknown revision %s') % revision except EmptyRepositoryError: return _('Empty repository') except (ImproperArchiveTypeError, KeyError): return _('Unknown archive type') from kallithea import CONFIG rev_name = cs.raw_id[:12] archive_name = '%s-%s%s' % (repo_name.replace('/', '_'), rev_name, ext) archive_path = None cached_archive_path = None archive_cache_dir = CONFIG.get('archive_cache_dir') if archive_cache_dir and not subrepos: # TODO: subrepo caching? if not os.path.isdir(archive_cache_dir): os.makedirs(archive_cache_dir) cached_archive_path = os.path.join(archive_cache_dir, archive_name) if os.path.isfile(cached_archive_path): log.debug('Found cached archive in %s', cached_archive_path) archive_path = cached_archive_path else: log.debug('Archive %s is not yet cached', archive_name) if archive_path is None: # generate new archive fd, archive_path = tempfile.mkstemp() log.debug('Creating new temp archive in %s', archive_path) with os.fdopen(fd, 'wb') as stream: cs.fill_archive(stream=stream, kind=fileformat, subrepos=subrepos) # stream (and thus fd) has been closed by cs.fill_archive if cached_archive_path is not None: # we generated the archive - move it to cache log.debug('Storing new archive in %s', cached_archive_path) shutil.move(archive_path, cached_archive_path) archive_path = cached_archive_path def get_chunked_archive(archive_path): stream = open(archive_path, 'rb') while True: data = stream.read(16 * 1024) if not data: break yield data stream.close() if archive_path != cached_archive_path: log.debug('Destroying temp archive %s', archive_path) os.remove(archive_path) action_logger(user=request.authuser, action='user_downloaded_archive:%s' % (archive_name), repo=repo_name, ipaddr=request.ip_addr, commit=True) response.content_disposition = str('attachment; filename=%s' % (archive_name)) response.content_type = str(content_type) return get_chunked_archive(archive_path)
def get_cs_links(): revs_limit = 3 # display this amount always revs_top_limit = 50 # show upto this amount of changesets hidden revs_ids = action_params.split(',') deleted = user_log.repository is None if deleted: return ','.join(revs_ids) repo_name = user_log.repository.repo_name def lnk(rev, repo_name): lazy_cs = False title_ = None url_ = '#' if isinstance(rev, BaseChangeset) or isinstance( rev, AttributeDict): if rev.op and rev.ref_name: if rev.op == 'delete_branch': lbl = _('Deleted branch: %s') % rev.ref_name elif rev.op == 'tag': lbl = _('Created tag: %s') % rev.ref_name else: lbl = 'Unknown operation %s' % rev.op else: lazy_cs = True lbl = rev.short_id[:8] url_ = url('changeset_home', repo_name=repo_name, revision=rev.raw_id) else: # changeset cannot be found - it might have been stripped or removed lbl = rev[:12] title_ = _('Changeset %s not found') % lbl if parse_cs: return link_to(lbl, url_, title=title_, **{'data-toggle': 'tooltip'}) return link_to(lbl, url_, class_='lazy-cs' if lazy_cs else '', **{ 'data-raw_id': rev.raw_id, 'data-repo_name': repo_name }) def _get_op(rev_txt): _op = None _name = rev_txt if len(rev_txt.split('=>')) == 2: _op, _name = rev_txt.split('=>') return _op, _name revs = [] if len(filter(lambda v: v != '', revs_ids)) > 0: repo = None for rev in revs_ids[:revs_top_limit]: _op, _name = _get_op(rev) # we want parsed changesets, or new log store format is bad if parse_cs: try: if repo is None: repo = user_log.repository.scm_instance _rev = repo.get_changeset(rev) revs.append(_rev) except ChangesetDoesNotExistError: log.error('cannot find revision %s in this repo', rev) revs.append(rev) else: _rev = AttributeDict({ 'short_id': rev[:12], 'raw_id': rev, 'message': '', 'op': _op, 'ref_name': _name }) revs.append(_rev) cs_links = [ " " + ', '.join([lnk(rev, repo_name) for rev in revs[:revs_limit]]) ] _op1, _name1 = _get_op(revs_ids[0]) _op2, _name2 = _get_op(revs_ids[-1]) _rev = '%s...%s' % (_name1, _name2) compare_view = ( ' <div class="compare_view" data-toggle="tooltip" title="%s">' '<a href="%s">%s</a> </div>' % (_('Show all combined changesets %s->%s') % (revs_ids[0][:12], revs_ids[-1][:12]), url('changeset_home', repo_name=repo_name, revision=_rev), _('Compare view'))) # if we have exactly one more than normally displayed # just display it, takes less space than displaying # "and 1 more revisions" if len(revs_ids) == revs_limit + 1: cs_links.append(", " + lnk(revs[revs_limit], repo_name)) # hidden-by-default ones if len(revs_ids) > revs_limit + 1: uniq_id = revs_ids[0] html_tmpl = ('<span> %s <a class="show_more" id="_%s" ' 'href="#more">%s</a> %s</span>') if not feed: cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') % (len(revs_ids) - revs_limit), _('revisions'))) if not feed: html_tmpl = '<span id="%s" style="display:none">, %s </span>' else: html_tmpl = '<span id="%s"> %s </span>' morelinks = ', '.join( [lnk(rev, repo_name) for rev in revs[revs_limit:]]) if len(revs_ids) > revs_top_limit: morelinks += ', ...' cs_links.append(html_tmpl % (uniq_id, morelinks)) if len(revs) > 1: cs_links.append(compare_view) return ''.join(cs_links)
def edit(self, repo_name, revision, f_path): repo = c.db_repo # check if revision is a branch identifier- basically we cannot # create multiple heads via file editing _branches = repo.scm_instance.branches # check if revision is a branch name or branch hash if revision not in _branches and revision not in _branches.values(): h.flash(_('You can only edit files with revision ' 'being a valid branch'), category='warning') raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip', f_path=f_path)) r_post = request.POST c.cs = self.__get_cs(revision) c.file = self.__get_filenode(c.cs, f_path) if c.file.is_binary: raise HTTPFound(location=url('files_home', repo_name=c.repo_name, revision=c.cs.raw_id, f_path=f_path)) c.default_message = _('Edited file %s via Kallithea') % (f_path) c.f_path = f_path if r_post: old_content = safe_str(c.file.content) sl = old_content.splitlines(1) first_line = sl[0] if sl else '' # modes: 0 - Unix, 1 - Mac, 2 - DOS mode = detect_mode(first_line, 0) content = convert_line_endings(r_post.get('content', ''), mode) message = r_post.get('message') or c.default_message author = request.authuser.full_contact if content == old_content: h.flash(_('No changes'), category='warning') raise HTTPFound(location=url( 'changeset_home', repo_name=c.repo_name, revision='tip')) try: self.scm_model.commit_change(repo=c.db_repo_scm_instance, repo_name=repo_name, cs=c.cs, user=request.authuser.user_id, ip_addr=request.ip_addr, author=author, message=message, content=content, f_path=f_path) h.flash(_('Successfully committed to %s') % f_path, category='success') except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during commit'), category='error') raise HTTPFound(location=url( 'changeset_home', repo_name=c.repo_name, revision='tip')) return render('files/files_edit.html')
def get_fork_name(): repo_name = action_params url_ = url('summary_home', repo_name=repo_name) return _('Fork name %s') % link_to(action_params, url_)
def title(self): return _('Logging')
def fancy_file_stats(stats): """ Displays a fancy two colored bar for number of added/deleted lines of code on file :param stats: two element list of added/deleted lines of code """ from kallithea.lib.diffs import NEW_FILENODE, DEL_FILENODE, \ MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE a, d = stats['added'], stats['deleted'] width = 100 if stats['binary']: #binary mode lbl = '' bin_op = 1 if BIN_FILENODE in stats['ops']: lbl = 'bin+' if NEW_FILENODE in stats['ops']: lbl += _('new file') bin_op = NEW_FILENODE elif MOD_FILENODE in stats['ops']: lbl += _('mod') bin_op = MOD_FILENODE elif DEL_FILENODE in stats['ops']: lbl += _('del') bin_op = DEL_FILENODE elif RENAMED_FILENODE in stats['ops']: lbl += _('rename') bin_op = RENAMED_FILENODE #chmod can go with other operations if CHMOD_FILENODE in stats['ops']: _org_lbl = _('chmod') lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl #import ipdb;ipdb.set_trace() b_d = '<div class="bin bin%s progress-bar" style="width:100%%">%s</div>' % ( bin_op, lbl) b_a = '<div class="bin bin1" style="width:0%"></div>' return literal('<div style="width:%spx" class="progress">%s%s</div>' % (width, b_a, b_d)) t = stats['added'] + stats['deleted'] unit = float(width) / (t or 1) # needs > 9% of width to be visible or 0 to be hidden a_p = max(9, unit * a) if a > 0 else 0 d_p = max(9, unit * d) if d > 0 else 0 p_sum = a_p + d_p if p_sum > width: #adjust the percentage to be == 100% since we adjusted to 9 if a_p > d_p: a_p = a_p - (p_sum - width) else: d_p = d_p - (p_sum - width) a_v = a if a > 0 else '' d_v = d if d > 0 else '' d_a = '<div class="added progress-bar" style="width:%s%%">%s</div>' % (a_p, a_v) d_d = '<div class="deleted progress-bar" style="width:%s%%">%s</div>' % ( d_p, d_v) return literal( '<div class="pull-right progress" style="width:%spx">%s%s</div>' % (width, d_a, d_d))
def post(self, title, category, question, answer_type, interested_response, **kw): user = request.identity['user'] qa = model.Qa.query.get(_id=ObjectId(question)) # CASO BASE in cui risco a creare un filtro semplice per definizione e' quella di che venga solamente selezionata una risposta if len(interested_response) == 1: # La risposta e' solo una creo un filtro semplice created_precondition = model.Precondition( _owner=user._id, _category=ObjectId(category), title=title, type='simple', condition=[ObjectId(question), interested_response[0]]) else: # CASO AVANZATO sono state selezionate piu' risposte, devo prima creare tutte i filtri semplici e poi creare quella complessa if answer_type == "have_response": # Create one precondition simple for all possibility answer to question # After that create a complex precondition with previous simple precondition interested_response = qa.answers if answer_type == "what_response": # Create one precondition simple for all selected answer to question # After that create a complex precondition with previous simple precondition if len(interested_response) <= 1: response.status_code = 412 return dict( errors={ 'interested_response': _('Please select at least one answer') }) base_precond = [] for resp in interested_response: prec = model.Precondition(_owner=user._id, _category=ObjectId(category), title="%s_%s" % (qa.title.upper(), resp.upper()), type='simple', condition=[ObjectId(question), resp], public=True, visible=False) base_precond.append(prec) condition = [] for prc in base_precond[:-1]: condition.append(prc._id) condition.append('or') condition.append(base_precond[-1]._id) created_precondition = model.Precondition( _owner=user._id, _category=ObjectId(category), title=title, type='advanced', condition=condition) #flash(_("Now you can create an output <a href='%s'>HERE</a>" % lurl('/output?workspace='+ str(category)))) return dict(precondition_id=str(created_precondition._id), errors=None)
def __init__(self, org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers): from kallithea.controllers.compare import CompareController reviewers = set(reviewers) _assert_valid_reviewers(reviewers) (org_ref_type, org_ref_name, org_rev) = org_ref.split(':') org_display = h.short_ref(org_ref_type, org_ref_name) if org_ref_type == 'rev': cs = org_repo.scm_instance.get_changeset(org_rev) org_ref = 'branch:%s:%s' % (cs.branch, cs.raw_id) (other_ref_type, other_ref_name, other_rev) = other_ref.split(':') if other_ref_type == 'rev': cs = other_repo.scm_instance.get_changeset(other_rev) other_ref_name = cs.raw_id[:12] other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, cs.raw_id) other_display = h.short_ref(other_ref_type, other_ref_name) cs_ranges, _cs_ranges_not, ancestor_revs = \ CompareController._get_changesets(org_repo.scm_instance.alias, other_repo.scm_instance, other_rev, # org and other "swapped" org_repo.scm_instance, org_rev, ) if not cs_ranges: raise self.Empty(_('Cannot create empty pull request')) if not ancestor_revs: ancestor_rev = org_repo.scm_instance.EMPTY_CHANGESET elif len(ancestor_revs) == 1: ancestor_rev = ancestor_revs[0] else: raise self.AmbiguousAncestor( _('Cannot create pull request - criss cross merge detected, please merge a later %s revision to %s') % (other_ref_name, org_ref_name)) self.revisions = [cs_.raw_id for cs_ in cs_ranges] # hack: ancestor_rev is not an other_rev but we want to show the # requested destination and have the exact ancestor other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, ancestor_rev) if not title: if org_repo == other_repo: title = '%s to %s' % (org_display, other_display) else: title = '%s#%s to %s#%s' % (org_repo.repo_name, org_display, other_repo.repo_name, other_display) description = description or _('No description') self.org_repo = org_repo self.other_repo = other_repo self.org_ref = org_ref self.org_rev = org_rev self.other_ref = other_ref self.title = title self.description = description self.owner = owner self.reviewers = reviewers if not CreatePullRequestAction.is_user_authorized(self.org_repo, self.other_repo): raise self.Unauthorized(_('You are not authorized to create the pull request'))
def title(self): return _('Controllers')
def __init__(self, old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers): self.old_pull_request = old_pull_request org_repo = old_pull_request.org_repo org_ref_type, org_ref_name, org_rev = old_pull_request.org_ref.split(':') other_repo = old_pull_request.other_repo other_ref_type, other_ref_name, other_rev = old_pull_request.other_ref.split(':') # other_rev is ancestor #assert other_ref_type == 'branch', other_ref_type # TODO: what if not? new_org_ref = '%s:%s:%s' % (org_ref_type, org_ref_name, new_org_rev) new_other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, new_other_rev) self.create_action = CreatePullRequestAction(org_repo, other_repo, new_org_ref, new_other_ref, None, None, owner, reviewers) # Generate complete title/description old_revisions = set(old_pull_request.revisions) revisions = self.create_action.revisions new_revisions = [r for r in revisions if r not in old_revisions] lost = old_revisions.difference(revisions) infos = ['This is a new iteration of %s "%s".' % (h.canonical_url('pullrequest_show', repo_name=old_pull_request.other_repo.repo_name, pull_request_id=old_pull_request.pull_request_id), old_pull_request.title)] if lost: infos.append(_('Missing changesets since the previous iteration:')) for r in old_pull_request.revisions: if r in lost: rev_desc = org_repo.get_changeset(r).message.split('\n')[0] infos.append(' %s %s' % (h.short_id(r), rev_desc)) if new_revisions: infos.append(_('New changesets on %s %s since the previous iteration:') % (org_ref_type, org_ref_name)) for r in reversed(revisions): if r in new_revisions: rev_desc = org_repo.get_changeset(r).message.split('\n')[0] infos.append(' %s %s' % (h.short_id(r), h.shorter(rev_desc, 80))) if self.create_action.other_ref == old_pull_request.other_ref: infos.append(_("Ancestor didn't change - diff since previous iteration:")) infos.append(h.canonical_url('compare_url', repo_name=org_repo.repo_name, # other_repo is always same as repo_name org_ref_type='rev', org_ref_name=h.short_id(org_rev), # use old org_rev as base other_ref_type='rev', other_ref_name=h.short_id(new_org_rev), )) # note: linear diff, merge or not doesn't matter else: infos.append(_('This iteration is based on another %s revision and there is no simple diff.') % other_ref_name) else: infos.append(_('No changes found on %s %s since previous iteration.') % (org_ref_type, org_ref_name)) # TODO: fail? v = 2 m = re.match(r'(.*)\(v(\d+)\)\s*$', title) if m is not None: title = m.group(1) v = int(m.group(2)) + 1 self.create_action.title = '%s (v%s)' % (title.strip(), v) # using a mail-like separator, insert new iteration info in description with latest first descriptions = description.replace('\r\n', '\n').split('\n-- \n', 1) description = descriptions[0].strip() + '\n\n-- \n' + '\n'.join(infos) if len(descriptions) > 1: description += '\n\n' + descriptions[1].strip() self.create_action.description = description if not CreatePullRequestIterationAction.is_user_authorized(self.old_pull_request): raise CreatePullRequestAction.Unauthorized(_('You are not authorized to create the pull request'))
def compare(self, repo_name, org_ref_type, org_ref_name, other_ref_type, other_ref_name): org_ref_name = org_ref_name.strip() other_ref_name = other_ref_name.strip() # If merge is True: # Show what org would get if merged with other: # List changesets that are ancestors of other but not of org. # New changesets in org is thus ignored. # Diff will be from common ancestor, and merges of org to other will thus be ignored. # If merge is False: # Make a raw diff from org to other, no matter if related or not. # Changesets in one and not in the other will be ignored merge = bool(request.GET.get('merge')) # fulldiff disables cut_off_limit c.fulldiff = request.GET.get('fulldiff') # partial uses compare_cs.html template directly partial = request.environ.get('HTTP_X_PARTIAL_XHR') # is_ajax_preview puts hidden input field with changeset revisions c.is_ajax_preview = partial and request.GET.get('is_ajax_preview') # swap url for compare_diff page - never partial and never is_ajax_preview c.swap_url = h.url('compare_url', repo_name=c.cs_repo.repo_name, org_ref_type=other_ref_type, org_ref_name=other_ref_name, other_repo=c.a_repo.repo_name, other_ref_type=org_ref_type, other_ref_name=org_ref_name, merge=merge or '') # set callbacks for generating markup for icons c.ignorews_url = _ignorews_url c.context_url = _context_url ignore_whitespace = request.GET.get('ignorews') == '1' line_context = safe_int(request.GET.get('context'), 3) c.a_rev = self._get_ref_rev(c.a_repo, org_ref_type, org_ref_name, returnempty=True) c.cs_rev = self._get_ref_rev(c.cs_repo, other_ref_type, other_ref_name) c.compare_home = False c.a_ref_name = org_ref_name c.a_ref_type = org_ref_type c.cs_ref_name = other_ref_name c.cs_ref_type = other_ref_type c.cs_ranges, c.cs_ranges_org, c.ancestors = self._get_changesets( c.a_repo.scm_instance.alias, c.a_repo.scm_instance, c.a_rev, c.cs_repo.scm_instance, c.cs_rev) raw_ids = [x.raw_id for x in c.cs_ranges] c.cs_comments = c.cs_repo.get_comments(raw_ids) c.statuses = c.cs_repo.statuses(raw_ids) revs = [ctx.revision for ctx in reversed(c.cs_ranges)] c.jsdata = graph_data(c.cs_repo.scm_instance, revs) if partial: return render('compare/compare_cs.html') org_repo = c.a_repo other_repo = c.cs_repo if merge: rev1 = msg = None if not c.cs_ranges: msg = _('Cannot show empty diff') elif not c.ancestors: msg = _('No ancestor found for merge diff') elif len(c.ancestors) == 1: rev1 = c.ancestors[0] else: msg = _('Multiple merge ancestors found for merge compare') if rev1 is None: h.flash(msg, category='error') log.error(msg) raise HTTPNotFound # case we want a simple diff without incoming changesets, # previewing what will be merged. # Make the diff on the other repo (which is known to have other_rev) log.debug('Using ancestor %s as rev1 instead of %s', rev1, c.a_rev) org_repo = other_repo else: # comparing tips, not necessarily linearly related if org_repo != other_repo: # TODO: we could do this by using hg unionrepo log.error('cannot compare across repos %s and %s', org_repo, other_repo) h.flash(_( 'Cannot compare repositories without using common ancestor' ), category='error') raise HTTPBadRequest rev1 = c.a_rev diff_limit = self.cut_off_limit if not c.fulldiff else None log.debug('running diff between %s and %s in %s', rev1, c.cs_rev, org_repo.scm_instance.path) txtdiff = org_repo.scm_instance.get_diff( rev1=rev1, rev2=c.cs_rev, ignore_whitespace=ignore_whitespace, context=line_context) diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', diff_limit=diff_limit) _parsed = diff_processor.prepare() c.limited_diff = False if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True c.file_diff_data = [] c.lines_added = 0 c.lines_deleted = 0 for f in _parsed: st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] filename = f['filename'] fid = h.FID('', filename) diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f]) c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, diff, st)) return render('compare/compare_diff.html')
def make_description(self, notification, show_age=True): """ Creates a human readable description based on properties of notification object """ #alias _n = notification if show_age: return { _n.TYPE_CHANGESET_COMMENT: _('%(user)s commented on changeset %(age)s'), _n.TYPE_MESSAGE: _('%(user)s sent message %(age)s'), _n.TYPE_MENTION: _('%(user)s mentioned you %(age)s'), _n.TYPE_REGISTRATION: _('%(user)s registered in Kallithea %(age)s'), _n.TYPE_PULL_REQUEST: _('%(user)s opened new pull request %(age)s'), _n.TYPE_PULL_REQUEST_COMMENT: _('%(user)s commented on pull request %(age)s'), }[notification.type_] % dict( user=notification.created_by_user.username, age=h.age(notification.created_on), ) else: return { _n.TYPE_CHANGESET_COMMENT: _('%(user)s commented on changeset at %(when)s'), _n.TYPE_MESSAGE: _('%(user)s sent message at %(when)s'), _n.TYPE_MENTION: _('%(user)s mentioned you at %(when)s'), _n.TYPE_REGISTRATION: _('%(user)s registered in Kallithea at %(when)s'), _n.TYPE_PULL_REQUEST: _('%(user)s opened new pull request at %(when)s'), _n.TYPE_PULL_REQUEST_COMMENT: _('%(user)s commented on pull request at %(when)s'), }[notification.type_] % dict( user=notification.created_by_user.username, when=h.fmt_date(notification.created_on), )
def create_cs_pr_comment(repo_name, revision=None, pull_request=None, allowed_to_change_status=True): """ Add a comment to the specified changeset or pull request, using POST values from the request. Comments can be inline (when a file path and line number is specified in POST) or general comments. A comment can be accompanied by a review status change (accepted, rejected, etc.). Pull requests can be closed or deleted. Parameter 'allowed_to_change_status' is used for both status changes and closing of pull requests. For deleting of pull requests, more specific checks are done. """ assert request.environ.get('HTTP_X_PARTIAL_XHR') if pull_request: pull_request_id = pull_request.pull_request_id else: pull_request_id = None status = request.POST.get('changeset_status') close_pr = request.POST.get('save_close') delete = request.POST.get('save_delete') f_path = request.POST.get('f_path') line_no = request.POST.get('line') if (status or close_pr or delete) and (f_path or line_no): # status votes and closing is only possible in general comments raise HTTPBadRequest() if not allowed_to_change_status: if status or close_pr: h.flash(_('No permission to change status'), 'error') raise HTTPForbidden() if pull_request and delete == "delete": if (pull_request.owner_id == request.authuser.user_id or h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionLevel('admin')( pull_request.org_repo.repo_name) or h.HasRepoPermissionLevel('admin')( pull_request.other_repo.repo_name) ) and not pull_request.is_closed(): PullRequestModel().delete(pull_request) Session().commit() h.flash(_('Successfully deleted pull request %s') % pull_request_id, category='success') return { 'location': h.url('my_pullrequests'), # or repo pr list? } raise HTTPForbidden() text = request.POST.get('text', '').strip() comment = ChangesetCommentsModel().create( text=text, repo=c.db_repo.repo_id, author=request.authuser.user_id, revision=revision, pull_request=pull_request_id, f_path=f_path or None, line_no=line_no or None, status_change=ChangesetStatus.get_status_lbl(status) if status else None, closing_pr=close_pr, ) if status: ChangesetStatusModel().set_status( c.db_repo.repo_id, status, request.authuser.user_id, comment, revision=revision, pull_request=pull_request_id, ) if pull_request: action = 'user_commented_pull_request:%s' % pull_request_id else: action = 'user_commented_revision:%s' % revision action_logger(request.authuser, action, c.db_repo, request.ip_addr) if pull_request and close_pr: PullRequestModel().close_pull_request(pull_request_id) action_logger(request.authuser, 'user_closed_pull_request:%s' % pull_request_id, c.db_repo, request.ip_addr) Session().commit() data = { 'target_id': h.safeid(request.POST.get('f_path')), } if comment is not None: c.comment = comment data.update(comment.get_dict()) data.update({ 'rendered_text': render('changeset/changeset_comment_block.html') }) return data
def post_logout(self, came_from=lurl('/')): flash(_('We hope to see you soon!')) return HTTPFound(location=came_from)