pagure-new/pagure/ui/app.py

686 lines
19 KiB
Python

# -*- coding: utf-8 -*-
"""
(c) 2014-2015 - Copyright Red Hat Inc
Authors:
Pierre-Yves Chibon <pingou@pingoured.fr>
"""
import flask
from math import ceil
from sqlalchemy.exc import SQLAlchemyError
import pagure.exceptions
import pagure.lib
import pagure.lib.git
import pagure.forms
import pagure.ui.filters
from pagure import (APP, SESSION, login_required,
authenticated,
admin_session_timedout)
# Application
# pylint: disable=no-member
@APP.route('/browse/projects', endpoint='browse_projects')
@APP.route('/browse/projects/', endpoint='browse_projects')
@APP.route('/')
def index():
""" Front page of the application.
"""
sorting = flask.request.args.get('sorting') or None
page = flask.request.args.get('page', 1)
try:
page = int(page)
if page < 1:
page = 1
except ValueError:
page = 1
limit = APP.config['ITEM_PER_PAGE']
start = limit * (page - 1)
repos = pagure.lib.search_projects(
SESSION,
fork=False,
start=start,
limit=limit,
sort=sorting)
num_repos = pagure.lib.search_projects(
SESSION,
fork=False,
count=True)
total_page = int(ceil(num_repos / float(limit)))
if authenticated() and flask.request.path == '/':
return index_auth()
return flask.render_template(
'index.html',
select="projects",
repos=repos,
repos_length=num_repos,
total_page=total_page,
page=page,
sorting=sorting,
)
def index_auth():
""" Front page for authenticated user.
"""
user = pagure.lib.search_user(SESSION, username=flask.g.fas_user.username)
if not user:
flask.abort(404, 'No user `%s` found, re-login maybe?' % (
flask.g.fas_user.username))
repopage = flask.request.args.get('repopage', 1)
try:
repopage = int(repopage)
if repopage < 1:
repopage = 1
except ValueError:
repopage = 1
forkpage = flask.request.args.get('forkpage', 1)
try:
forkpage = int(forkpage)
if forkpage < 1:
forkpage = 1
except ValueError:
forkpage = 1
repos = pagure.lib.search_projects(
SESSION,
username=flask.g.fas_user.username,
fork=False)
repos_length = pagure.lib.search_projects(
SESSION,
username=flask.g.fas_user.username,
fork=False,
count=True)
forks = pagure.lib.search_projects(
SESSION,
username=flask.g.fas_user.username,
fork=True)
forks_length = pagure.lib.search_projects(
SESSION,
username=flask.g.fas_user.username,
fork=True,
count=True)
watch_list = pagure.lib.user_watch_list(
SESSION,
user=flask.g.fas_user.username)
return flask.render_template(
'index_auth.html',
username=flask.g.fas_user.username,
user=user,
forks=forks,
repos=repos,
watch_list=watch_list,
repopage=repopage,
forkpage=forkpage,
repos_length=repos_length,
forks_length=forks_length,
)
@APP.route('/search/')
@APP.route('/search')
def search():
""" Search this pagure instance for projects or users.
"""
stype = flask.request.args.get('type', 'projects')
term = flask.request.args.get('term')
page = flask.request.args.get('page', 1)
try:
page = int(page)
if page < 1:
page = 1
except ValueError:
page = 1
if stype == 'projects':
return flask.redirect(flask.url_for('view_projects', pattern=term))
elif stype == 'projects_forks':
return flask.redirect(flask.url_for(
'view_projects', pattern=term, forks=True))
else:
return flask.redirect(flask.url_for('view_users', username=term))
@APP.route('/users/')
@APP.route('/users')
@APP.route('/users/<username>')
def view_users(username=None):
""" Present the list of users.
"""
page = flask.request.args.get('page', 1)
try:
page = int(page)
if page < 1:
page = 1
except ValueError:
page = 1
users = pagure.lib.search_user(SESSION, pattern=username)
if len(users) == 1:
flask.flash('Only one result found, redirecting you to it')
return flask.redirect(
flask.url_for('view_user', username=users[0].username))
limit = APP.config['ITEM_PER_PAGE']
start = limit * (page - 1)
end = limit * page
users_length = len(users)
users = users[start:end]
total_page = int(ceil(users_length / float(limit)))
for user in users:
repos_length = pagure.lib.search_projects(
SESSION,
username=user.user,
fork=False,
count=True)
forks_length = pagure.lib.search_projects(
SESSION,
username=user.user,
fork=True,
count=True)
user.repos_length = repos_length
user.forks_length = forks_length
return flask.render_template(
'user_list.html',
users=users,
users_length=users_length,
total_page=total_page,
page=page,
select='users',
)
@APP.route('/projects/')
@APP.route('/projects')
@APP.route('/projects/<pattern>')
@APP.route('/projects/<namespace>/<pattern>')
def view_projects(pattern=None, namespace=None):
""" Present the list of projects.
"""
forks = flask.request.args.get('forks')
page = flask.request.args.get('page', 1)
try:
page = int(page)
if page < 1:
page = 1
except ValueError:
page = 1
select = 'projects'
# If forks is specified, we want both forks and projects
if str(forks).lower() in ['true', '1']:
forks = None
select = 'projects_forks'
else:
forks = False
limit = APP.config['ITEM_PER_PAGE']
start = limit * (page - 1)
projects = pagure.lib.search_projects(
SESSION, pattern=pattern, namespace=namespace,
fork=forks, start=start, limit=limit)
if len(projects) == 1:
flask.flash('Only one result found, redirecting you to it')
return flask.redirect(flask.url_for(
'view_repo', repo=projects[0].name,
username=projects[0].user.username if projects[0].is_fork else None
))
limit = APP.config['ITEM_PER_PAGE']
start = limit * (page - 1)
end = limit * page
projects_length = len(projects)
projects = projects[start:end]
total_page = int(ceil(projects_length / float(limit)))
return flask.render_template(
'index.html',
repos=projects,
repos_length=projects_length,
total_page=total_page,
page=page,
select=select,
)
@APP.route('/user/<username>/')
@APP.route('/user/<username>')
def view_user(username):
""" Front page of a specific user.
"""
user = pagure.lib.search_user(SESSION, username=username)
if not user:
flask.abort(404, 'No user `%s` found' % username)
repopage = flask.request.args.get('repopage', 1)
try:
repopage = int(repopage)
if repopage < 1:
repopage = 1
except ValueError:
repopage = 1
forkpage = flask.request.args.get('forkpage', 1)
try:
forkpage = int(forkpage)
if forkpage < 1:
forkpage = 1
except ValueError:
forkpage = 1
limit = APP.config['ITEM_PER_PAGE']
repo_start = limit * (repopage - 1)
fork_start = limit * (forkpage - 1)
repos = pagure.lib.search_projects(
SESSION,
username=username,
fork=False,
start=repo_start,
limit=limit)
repos_length = pagure.lib.search_projects(
SESSION,
username=username,
fork=False,
count=True)
forks = pagure.lib.search_projects(
SESSION,
username=username,
fork=True,
start=fork_start,
limit=limit)
forks_length = pagure.lib.search_projects(
SESSION,
username=username,
fork=True,
count=True)
total_page_repos = int(ceil(repos_length / float(limit)))
total_page_forks = int(ceil(forks_length / float(limit)))
return flask.render_template(
'user_info.html',
username=username,
user=user,
repos=repos,
total_page_repos=total_page_repos,
forks=forks,
total_page_forks=total_page_forks,
repopage=repopage,
forkpage=forkpage,
repos_length=repos_length,
forks_length=forks_length,
)
@APP.route('/user/<username>/requests/')
@APP.route('/user/<username>/requests')
def view_user_requests(username):
""" Shows the pull-requests for the specified user.
"""
user = pagure.lib.search_user(SESSION, username=username)
if not user:
flask.abort(404, 'No user `%s` found' % username)
requests = pagure.lib.get_pull_request_of_user(
SESSION,
username=username
)
return flask.render_template(
'user_requests.html',
username=username,
user=user,
requests=requests,
)
@APP.route('/new/', methods=('GET', 'POST'))
@APP.route('/new', methods=('GET', 'POST'))
@login_required
def new_project():
""" Form to create a new project.
"""
user = pagure.lib.search_user(SESSION, username=flask.g.fas_user.username)
if not pagure.APP.config.get('ENABLE_NEW_PROJECTS', True):
flask.abort(404, 'Creation of new project is not allowed on this \
pagure instance')
namespaces = APP.config['ALLOWED_PREFIX'][:]
if user:
namespaces.extend([grp for grp in user.groups])
form = pagure.forms.ProjectForm(namespaces=namespaces)
if form.validate_on_submit():
name = form.name.data
description = form.description.data
url = form.url.data
avatar_email = form.avatar_email.data
create_readme = form.create_readme.data
namespace = form.namespace.data
if namespace:
namespace = namespace.strip()
try:
pagure.lib.new_project(
SESSION,
name=name,
description=description,
namespace=namespace,
url=url,
avatar_email=avatar_email,
user=flask.g.fas_user.username,
blacklist=APP.config['BLACKLISTED_PROJECTS'],
allowed_prefix=APP.config['ALLOWED_PREFIX'],
gitfolder=APP.config['GIT_FOLDER'],
docfolder=APP.config['DOCS_FOLDER'],
ticketfolder=APP.config['TICKETS_FOLDER'],
requestfolder=APP.config['REQUESTS_FOLDER'],
add_readme=create_readme,
userobj=user,
prevent_40_chars=APP.config.get(
'OLD_VIEW_COMMIT_ENABLED', False),
)
SESSION.commit()
pagure.lib.git.generate_gitolite_acls()
return flask.redirect(flask.url_for(
'view_repo', repo=name, namespace=namespace))
except pagure.exceptions.PagureException as err:
flask.flash(str(err), 'error')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
flask.flash(str(err), 'error')
return flask.render_template(
'new_project.html',
form=form,
)
@APP.route('/settings/', methods=('GET', 'POST'))
@APP.route('/settings', methods=('GET', 'POST'))
@login_required
def user_settings():
""" Update the user settings.
"""
if admin_session_timedout():
return flask.redirect(
flask.url_for('auth_login', next=flask.request.url))
user = pagure.lib.search_user(
SESSION, username=flask.g.fas_user.username)
if not user:
flask.abort(404, 'User not found')
form = pagure.forms.UserSettingsForm()
if form.validate_on_submit():
ssh_key = form.ssh_key.data
try:
message = 'Nothing to update'
if user.public_ssh_key != ssh_key:
pagure.lib.update_user_ssh(
SESSION,
user=user,
ssh_key=ssh_key,
keydir=APP.config.get('GITOLITE_KEYDIR', None),
)
SESSION.commit()
message = 'Public ssh key updated'
flask.flash(message)
return flask.redirect(
flask.url_for('.user_settings'))
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
flask.flash(str(err), 'error')
elif flask.request.method == 'GET':
form.ssh_key.data = user.public_ssh_key
return flask.render_template(
'user_settings.html',
user=user,
form=form,
)
@APP.route('/markdown/', methods=['POST'])
def markdown_preview():
""" Return the provided markdown text in html.
The text has to be provided via the parameter 'content' of a POST query.
"""
form = pagure.forms.ConfirmationForm()
if form.validate_on_submit():
return pagure.ui.filters.markdown_filter(flask.request.form['content'])
else:
flask.abort(400, 'Invalid request')
@APP.route('/settings/email/drop', methods=['POST'])
@login_required
def remove_user_email():
""" Remove the specified email from the logged in user.
"""
if admin_session_timedout():
return flask.redirect(
flask.url_for('auth_login', next=flask.request.url))
user = pagure.lib.search_user(
SESSION, username=flask.g.fas_user.username)
if not user:
flask.abort(404, 'User not found')
if len(user.emails) == 1:
flask.flash(
'You must always have at least one email', 'error')
return flask.redirect(
flask.url_for('.user_settings')
)
form = pagure.forms.UserEmailForm()
if form.validate_on_submit():
email = form.email.data
useremails = [mail.email for mail in user.emails]
if email not in useremails:
flask.flash(
'You do not have the email: %s, nothing to remove' % email,
'error')
return flask.redirect(
flask.url_for('.user_settings')
)
for mail in user.emails:
if mail.email == email:
user.emails.remove(mail)
break
try:
SESSION.commit()
flask.flash('Email removed')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash('Email could not be removed', 'error')
return flask.redirect(flask.url_for('.user_settings'))
@APP.route('/settings/email/add/', methods=['GET', 'POST'])
@APP.route('/settings/email/add', methods=['GET', 'POST'])
@login_required
def add_user_email():
""" Add a new email for the logged in user.
"""
if admin_session_timedout():
return flask.redirect(
flask.url_for('auth_login', next=flask.request.url))
user = pagure.lib.search_user(
SESSION, username=flask.g.fas_user.username)
if not user:
flask.abort(404, 'User not found')
form = pagure.forms.UserEmailForm(
emails=[mail.email for mail in user.emails])
if form.validate_on_submit():
email = form.email.data
try:
pagure.lib.add_user_pending_email(SESSION, user, email)
SESSION.commit()
flask.flash('Email pending validation')
return flask.redirect(flask.url_for('.user_settings'))
except pagure.exceptions.PagureException as err:
flask.flash(str(err), 'error')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash('Email could not be added', 'error')
return flask.render_template(
'user_emails.html',
user=user,
form=form,
)
@APP.route('/settings/email/default', methods=['POST'])
@login_required
def set_default_email():
""" Set the default email address of the user.
"""
if admin_session_timedout():
return flask.redirect(
flask.url_for('auth_login', next=flask.request.url))
user = pagure.lib.search_user(
SESSION, username=flask.g.fas_user.username)
if not user:
flask.abort(404, 'User not found')
form = pagure.forms.UserEmailForm()
if form.validate_on_submit():
email = form.email.data
useremails = [mail.email for mail in user.emails]
if email not in useremails:
flask.flash(
'You do not have the email: %s, nothing to set' % email,
'error')
return flask.redirect(
flask.url_for('.user_settings')
)
user.default_email = email
try:
SESSION.commit()
flask.flash('Default email set to: %s' % email)
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash('Default email could not be set', 'error')
return flask.redirect(flask.url_for('.user_settings'))
@APP.route('/settings/email/resend', methods=['POST'])
@login_required
def reconfirm_email():
""" Re-send the email address of the user.
"""
if admin_session_timedout():
return flask.redirect(
flask.url_for('auth_login', next=flask.request.url))
user = pagure.lib.search_user(
SESSION, username=flask.g.fas_user.username)
if not user:
flask.abort(404, 'User not found')
form = pagure.forms.UserEmailForm()
if form.validate_on_submit():
email = form.email.data
try:
pagure.lib.resend_pending_email(SESSION, user, email)
SESSION.commit()
flask.flash('Confirmation email re-sent')
except pagure.exceptions.PagureException as err:
flask.flash(str(err), 'error')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash('Confirmation email could not be re-sent', 'error')
return flask.redirect(flask.url_for('.user_settings'))
@APP.route('/settings/email/confirm/<token>/')
@APP.route('/settings/email/confirm/<token>')
def confirm_email(token):
""" Confirm a new email.
"""
if admin_session_timedout():
return flask.redirect(
flask.url_for('auth_login', next=flask.request.url))
email = pagure.lib.search_pending_email(SESSION, token=token)
if not email:
flask.flash('No email associated with this token.', 'error')
else:
try:
pagure.lib.add_email_to_user(SESSION, email.user, email.email)
SESSION.delete(email)
SESSION.commit()
flask.flash('Email validated')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
flask.flash(
'Could not set the account as active in the db, '
'please report this error to an admin', 'error')
APP.logger.exception(err)
return flask.redirect(flask.url_for('.user_settings'))
@APP.route('/ssh_info/')
@APP.route('/ssh_info')
def ssh_hostkey():
""" Endpoint returning information about the SSH hostkey and fingerprint
of the current pagure instance.
"""
return flask.render_template(
'doc_ssh_keys.html',
)