def list_projects(request): if not request.user.is_admin: raise HTTPForbidden() session = DBSession() projects = session.query(Project).order_by(Project.title).all() return {'api': TemplateAPI(request, _(u'Projects')), 'projects': projects}
def home(request): project_name = request.matchdict['project_name'] session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_VIEW_PROJECT, project): raise HTTPForbidden() can_participate = has_permission( request, PERM_PARTICIPATE_IN_PROJECT, project) can_manage_project = has_permission(request, PERM_MANAGE_PROJECT, project) issues = session.query(Issue.id).filter_by(project_id=project.id).\ filter(Issue.status!=ISSUE_STATUS_CLOSED) n_assigned = issues.filter_by(assignee=request.user.id).count() n_watching = 0 # FIXME n_not_assigned = issues.filter_by(assignee=None).count() search_form = make_simplified_search_form(project, session) recent_activity = get_recent_activity(session, project, request) return {'api': TemplateAPI(request, project.title), 'project': project, 'can_participate': can_participate, 'can_manage_project': can_manage_project, 'n_assigned': n_assigned, 'n_watching': n_watching, 'n_not_assigned': n_not_assigned, 'search_form': search_form, 'recent_activity': recent_activity}
def list_users(request): """List users (in the control panel).""" if not request.user.is_admin: raise HTTPForbidden() session = DBSession() # FIXME: paginate users = session.query(User).order_by(User.fullname).all() return {'api': TemplateAPI(request, _(u'Users')), 'users': users}
def has_permission(request, permission, context=None): """Return whether the current user is granted the given ``permission`` in this ``context``. Context must be a ``Project`` on which the permission is to be checked, or ``None`` if the permission is to be checked on the root level. In the latter case, only ``PERM_ADMIN_SITE`` may be checked (because other permissions do not make sense outside of projects). """ if permission not in ALL_PERMS: raise ValueError(u'Unknown permission: "%s"' % permission) if not context and permission != PERM_ADMIN_SITE: raise ValueError( u'Wrong permission on a site: "%s"' % permission) if context and permission == PERM_ADMIN_SITE: raise ValueError( u'Wrong permission on a project: "%s"' % permission) cache_key_admin = '_user_is_site_admin' if getattr(request, cache_key_admin, None): return True if context is None: cache_key = '_user_site_perms' else: cache_key = '_user_perms_%s' % context.name if getattr(request, cache_key, None) is not None: return permission in getattr(request, cache_key) # Shortcuts for public projects and anonymous users if context is not None and context.public and \ permission == PERM_VIEW_PROJECT: return True user_id = request.user.id if not user_id: return False session = DBSession() user_perms = () if request.user.is_admin: user_perms = PERMISSIONS_FOR_ROLE[ROLE_SITE_ADMIN] setattr(request, cache_key_admin, True) elif context is not None: try: row = session.query(Role).filter_by( user_id=user_id, project_id=context.id).one() except NoResultFound: pass else: user_perms = PERMISSIONS_FOR_ROLE[row.role] setattr(request, cache_key, user_perms) return permission in user_perms
def check_password(login, password): """Return the corresponding user if the given credentials are correct. """ session = DBSession() try: user = session.query(User).filter_by(login=login).one() except NoResultFound: return None if not user.validate_password(password): return None return user
def delete_project(request): if not request.user.is_admin: raise HTTPForbidden() project_id = int(request.POST.get('project_id')) session = DBSession() project = session.query(Project).filter_by(id=project_id).one() name, title = project.name, project.title session.delete(project) msg = _(u'Project "${name}" ("${title}") has been deleted.') % { 'name': name, 'title': title} request.session.flash(msg, 'success') url = request.route_url('projects') return HTTPSeeOther(location=url)
def edit_user_form(request, form=None): if not request.user.is_admin: raise HTTPForbidden() user_id = int(request.matchdict['user_id']) session = DBSession() try: user = session.query(User).filter_by(id=user_id).one() except NoResultFound: raise HTTPNotFound() if form is None: form = EditUserForm(obj=user) return {'api': TemplateAPI(request, user.fullname), 'user': user, 'form': form}
def add_user(request): if not request.user.is_admin: raise HTTPForbidden() form = AddUserForm(request.POST) if not form.validate(): return add_user_form(request, form) data = form.data data.pop('password_confirm') user = User(**data) session = DBSession() session.add(user) request.session.flash(_(u'User has been added.'), 'success') location = request.route_url('users') return HTTPSeeOther(location)
def all_projects(self): """Return all projects that the current user has access to.""" user = self.request.user session = DBSession() if user.is_admin: projects = session.query(Project) elif user.id: public = session.query(Project).filter_by(public=True) has_role = session.query(Project).join(Role).filter( Project.id == Role.project_id) projects = has_role.union(public) else: projects = session.query(Project).filter_by(public=True) return projects.order_by(Project.title).all()
def configure_statuses_form(request): project_name = request.matchdict['project_name'] session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_MANAGE_PROJECT, project): raise HTTPForbidden() used = [s[0] for s in session.query(distinct(Issue.status)).\ filter_by(project_id=project.id).all()] # FIXME: We may want to include default statuses in 'used' as well. return {'api': TemplateAPI(request, project.title), 'project': project, 'can_manage_project': True, 'used': used}
def configure_roles_form(request): project_name = request.matchdict['project_name'] session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_MANAGE_PROJECT, project): raise HTTPForbidden() roles = [] for role_id, label in ROLE_LABELS.items(): roles.append({'id': role_id, 'label': label}) user_roles = [] for role, user in session.query(Role, User).\ filter(Role.project_id==project.id).\ filter(Role.user_id==User.id).\ order_by(User.fullname): user_roles.append({'user_id': user.id, 'fullname': user.fullname, 'role': role.role}) if request.user.is_admin: ids_with_role = [u['user_id'] for u in user_roles] users_with_no_role = [{'id': 0, 'fullname': _(u'Select a user...')}] # 'ids_with_role' may be empty. SQLAlchemy informs us in this # case that # the IN-predicate on "users.id" was invoked with an empty # sequence. This results in a contradiction, which # nonetheless can be expensive to evaluate. # Hence the construction of a tuple that contains at least -1 (an # impossible user id). for user in session.query(User).\ filter(~User.id.in_((-1, ) + tuple(ids_with_role))).\ filter_by(is_admin=False).\ order_by(User.fullname): users_with_no_role.append({'id': user.id, 'fullname': user.fullname}) else: # Project managers are not allowed to grant a role to users # who do not have a prior role in the project. users_with_no_role = () return {'api': TemplateAPI(request, project.title), 'can_manage_project': True, 'project': project, 'roles': roles, 'user_roles': user_roles, 'users_with_no_role': users_with_no_role}
def configure(request): project_name = request.matchdict['project_name'] session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_MANAGE_PROJECT, project): raise HTTPForbidden() form = make_edit_project_form(request.POST) if not form.validate(): return configure_form(request, form) project.update(**form.data) msg = _(u'Changes have been saved.') request.session.flash(msg, 'success') url = request.route_url('project_configure', project_name=project.name) return HTTPSeeOther(url)
def configure_form(request, form=None): project_name = request.matchdict['project_name'] session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_MANAGE_PROJECT, project): raise HTTPForbidden() if form is None: data = {'title': project.title, 'public': project.public} form = make_edit_project_form(**data) return {'api': TemplateAPI(request, project.title), 'project': project, 'can_manage_project': True, 'form': form}
def view(request, form=None): project_name = request.matchdict['project_name'] try: issue_ref = int(request.matchdict['issue_ref']) except ValueError: # 'issue_ref' is not an integer. The route connected to this # view matched because no other route matched. This could be a # typo, for example: '/p/project-foo/confgure'. raise HTTPNotFound() session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_VIEW_PROJECT, project): raise HTTPForbidden() try: issue = session.query(Issue).filter_by( project_id=project.id, ref=issue_ref).one() except NoResultFound: raise HTTPNotFound() if form is None: data = {'assignee': issue.assignee, 'deadline': issue.deadline, 'kind': issue.kind, 'priority': issue.priority, 'status': issue.status, 'time_estimated': issue.time_estimated, 'time_billed': issue.time_billed, 'title': issue.title} form = make_add_change_form(project, session, **data) can_see_priv = has_permission( request, PERM_SEE_PRIVATE_TIMING_INFO, project) can_participate = has_permission( request, PERM_PARTICIPATE_IN_PROJECT, project) can_manage_project = has_permission(request, PERM_MANAGE_PROJECT, project) time_info = issue.get_time_info(include_private_info=can_see_priv) return {'api': TemplateAPI(request, issue.title), 'project': project, 'issue': issue, 'time_info': time_info, 'form': form, 'can_participate': can_participate, 'can_manage_project': can_manage_project, 'can_see_private_time_info': can_see_priv}
def list_user_roles(request): if not request.user.is_admin: raise HTTPForbidden() user_id = int(request.matchdict['user_id']) session = DBSession() try: user = session.query(User).filter_by(id=user_id).one() except NoResultFound: raise HTTPNotFound() roles = [] for role, project in session.query(Role, Project).\ filter(Role.user_id==user.id).\ filter(Role.project_id==Project.id).\ order_by(Project.title): roles.append((project, _(ROLE_LABELS[role.role]))) return {'api': TemplateAPI(request, user.fullname), 'user': user, 'roles': roles}
def recent_activity(request): project_name = request.matchdict['project_name'] session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_VIEW_PROJECT, project): raise HTTPForbidden() can_participate = has_permission( request, PERM_PARTICIPATE_IN_PROJECT, project) can_manage_project = has_permission(request, PERM_MANAGE_PROJECT, project) recent_activity = get_recent_activity(session, project, request) return {'api': TemplateAPI(request, project.title), 'project': project, 'can_participate': can_participate, 'can_manage_project': can_manage_project, 'recent_activity': recent_activity}
def add_form(request, form=None): session = DBSession() project_name = request.matchdict['project_name'] try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_PARTICIPATE_IN_PROJECT, project): raise HTTPForbidden() if form is None: form = make_add_issue_form(project, session) can_see_priv = has_permission( request, PERM_SEE_PRIVATE_TIMING_INFO, project) can_manage_project = has_permission(request, PERM_MANAGE_PROJECT, project) return {'api': TemplateAPI(request, _(u'Add issue')), 'project': project, 'form': form, 'can_see_private_time_info': can_see_priv, 'can_manage_project': can_manage_project}
def _get_user(request): """Return an instance of the ``User`` class if the user is logged in, or ``AnonymousUser`` otherwise. This function is never called explicitely by our code: it corresponds to the ``user`` reified property on requests (see ``app`` module where we call ``set_request_property()``). """ user = None user_id = authenticated_userid(request) if user_id: session = DBSession() try: user = session.query(User).filter_by(id=user_id).one() except NoResultFound: user = None if user is None: return _AnonymousUser() return user
def edit_user(request): if not request.user.is_admin: raise HTTPForbidden() user_id = int(request.matchdict['user_id']) session = DBSession() try: user = session.query(User).filter_by(id=user_id).one() except NoResultFound: raise HTTPNotFound() form = EditUserForm(request.POST) if not form.validate(): return edit_user_form(request, form) data = form.data if user_id == request.user.id and not form.data['is_admin']: msg = _(u"You cannot revoke your own administrator's rights.") request.session.flash(msg, 'error') return edit_user_form(request, form) user.update(**data) request.session.flash(_(u'User has been edited.'), 'success') location = request.route_url('users') return HTTPSeeOther(location)
def issues(request): """A list of issues that correspond to the filter requested in the GET parameters. """ project_name = request.matchdict['project_name'] session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_VIEW_PROJECT, project): raise HTTPForbidden() can_participate = has_permission( request, PERM_PARTICIPATE_IN_PROJECT, project) can_manage_project = has_permission(request, PERM_MANAGE_PROJECT, project) issues = session.query(Issue).filter_by(project_id=project.id).\ filter(Issue.status!=ISSUE_STATUS_CLOSED) filter = request.GET.get('filter') if filter == 'assigned-to-me': issues = issues.filter_by(assignee=request.user.id) filter_label = _('Assigned to me') elif filter == 'watching': # FIXME: not implemented yet filter_label = None elif filter == 'not-assigned': filter_label = _('Not assigned') issues = issues.filter_by(assignee=None) else: filter_label = None issues = () if issues: issues = issues.all() return {'api': TemplateAPI(request, project.title), 'project': project, 'can_participate': can_participate, 'can_manage_project': can_manage_project, 'filter': filter, 'filter_label': filter_label, 'issues': issues, 'count': len(issues)}
def add(request): session = DBSession() project_name = request.matchdict['project_name'] try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_PARTICIPATE_IN_PROJECT, project): raise HTTPForbidden() form = make_add_issue_form(project, session, request.POST) if not form.validate(): return add_form(request, form) last_ref = session.execute( 'SELECT MAX(ref) FROM issues ' 'WHERE project_id=%d' % project.id).fetchone()[0] if last_ref is None: last_ref = 0 ref = last_ref + 1 reporter = request.user.id now = datetime.utcnow() issue = Issue(project_id=project.id, date_created=now, date_edited=now, reporter=reporter, ref=ref) form.populate_obj(issue) session.add(issue) session.flush() change = Change(project_id=project.id, issue_id=issue.id, type=CHANGE_TYPE_OPENING, author=reporter, date=now, changes={}) form.populate_obj(change) session.add(change) route_args = {'project_name': project_name, 'issue_ref': issue.ref} url = request.route_url('issue_view', **route_args) return HTTPSeeOther(location=url)
def configure_statuses(request): project_name = request.matchdict['project_name'] session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_MANAGE_PROJECT, project): raise HTTPForbidden() posted_statuses = map(int, request.POST.getall('statuses')) # The UI should not allow to remove default statuses, but let's # enforce it here. for default_status in DEFAULT_STATUSES: if default_status['id'] not in posted_statuses: msg = _('You cannot remove this status.') request.session.flash(msg, 'error') return configure_statuses_form(request) # The UI should not allow to remove statuses that are being used, # but let's enforce it here. # FIXME: to do current_statuses = {} for status in project.statuses: current_statuses[status.id] = status statuses = zip(posted_statuses, request.POST.getall('labels')) # Change existing statuses and add new ones new_id = session.execute( 'SELECT MAX(id) FROM statuses ' 'WHERE project_id=%d' % project.id).fetchone()[0] for position, (status_id, label) in enumerate(statuses, 1): if not status_id: new_id += 1 status = Status(id=new_id, project_id=project.id, label=label, position=position) session.add(status) else: status = current_statuses[status_id] if label != status.label: status.label = label if position != status.position: status.position = position # Remove statuses for status in project.statuses: if status.id not in posted_statuses: session.delete(status) msg = _('Your changes have been saved.') request.session.flash(msg, 'success') url = request.route_url('project_configure_statuses', project_name=project.name) return HTTPSeeOther(location=url)
def configure_roles(request): project_name = request.matchdict['project_name'] session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_MANAGE_PROJECT, project): raise HTTPForbidden() current_roles = {} for role in session.query(Role).filter_by(project_id=project.id): current_roles[role.user_id] = role.role updated_roles = [] for field, role_id in request.POST.items(): if not field.startswith('role_'): # pragma: no cover continue user_id = int(field[1 + field.rfind('_'):]) if not request.user.is_admin: if user_id == request.user.id: # Manager cannot revoke his/her own manager role. This # is not allowed by the UI but we handle the case # anyway. msg = _(u'You cannot revoke your own manager role.') request.session.flash(msg, 'error') return configure_roles_form(request) if user_id not in current_roles: # A non-administrator cannot grant a role to a user # who has no prior role in this project. This is not # allowed by the UI but we handle the case anyway. msg = _(u'Granting a role to a new user is not allowed ' u'because you are not an administrator.') request.session.flash(msg, 'error') return configure_roles_form(request) role_id = int(role_id) if role_id: updated_roles.append(Role(project_id=project.id, user_id=user_id, role=role_id)) session.query(Role).filter_by(project_id=project.id).delete() session.add_all(updated_roles) request.session.flash(_(u'Roles have been updated'), 'success') location = request.route_url('project_configure_roles', project_name=project.name) return HTTPSeeOther(location=location)
def update(request): project_name = request.matchdict['project_name'] issue_ref = int(request.matchdict['issue_ref']) session = DBSession() try: project = session.query(Project).filter_by(name=project_name).one() except NoResultFound: raise HTTPNotFound() if not has_permission(request, PERM_PARTICIPATE_IN_PROJECT, project): raise HTTPForbidden() try: issue = session.query(Issue).filter_by( project_id=project.id, ref=issue_ref).one() except NoResultFound: raise HTTPNotFound() # FIXME: move logic outside so that it can be more easily tested. form = make_add_change_form(project, session, request.POST) if not form.validate(): return view(request, form) now = datetime.utcnow() userid = request.user.id changes = {} for attr in ( 'title', 'status', 'assignee', 'deadline', 'priority', 'kind', 'time_estimated', 'time_billed'): old_v = getattr(issue, attr) new_v = getattr(form, attr).data if old_v != new_v: changes[attr] = (old_v, new_v) setattr(issue, attr, new_v) if form.time_spent_real.data and \ has_permission(request, PERM_SEE_PRIVATE_TIMING_INFO, project): changes['time_spent_real'] = (None, form.time_spent_real.data) if form.time_spent_public.data: changes['time_spent_public'] = (None, form.time_spent_public.data) if not changes and not form.text.data: error = _(u'You did not provide any comment or update.') form.errors['form'] = [error] return view(request, form) if form.status.data == ISSUE_STATUS_OPEN and \ issue.status != ISSUE_STATUS_OPEN: change_type = CHANGE_TYPE_REOPENING elif form.status.data == ISSUE_STATUS_CLOSED: change_type = CHANGE_TYPE_CLOSING else: change_type = CHANGE_TYPE_UPDATE change = Change(project_id=project.id, issue_id=issue.id, type=change_type, author=userid, date=now, text=form.text.data, text_renderer=form.text_renderer.data, changes=changes) if 'time_spent_real' in changes: change.time_spent_real = form.time_spent_real.data if 'time_spent_public' in changes: change.time_spent_public = form.time_spent_public.data session.add(change) route_args = {'project_name': project_name, 'issue_ref': issue.ref, '_query': {'issue_updated': 1}, '_anchor': 'issue_updated'} url = request.route_url('issue_view', **route_args) return HTTPSeeOther(location=url)
def statuses(self): return dict(DBSession.query(Status.id, Status.label).all())
def fullnames(self): return dict(DBSession.query(User.id, User.fullname).all())
def validate_name(self, field): session = DBSession() if session.query(Project).filter_by(name=field.data).first(): raise ValidationError(u'This name is not available.')