"""
Routes configuration
The more specific and detailed routes should be defined first so they
may take precedent over the more generic routes. For more information
refer to the routes manual at http://routes.groovie.org/docs/
from __future__ import with_statement
from routes import Mapper
from rhodecode.lib.utils import check_repo_fast as cr
# prefix for non repository related links needs to be prefixed with `/`
ADMIN_PREFIX = '/_admin'
def make_map(config):
"""Create, configure and return the routes Mapper"""
rmap = Mapper(directory=config['pylons.paths']['controllers'],
always_scan=config['debug'])
rmap.minimization = False
rmap.explicit = False
def check_repo(environ, match_dict):
check for valid repository for proper 404 handling
:param environ:
:param match_dict:
repo_name = match_dict.get('repo_name')
return not cr(repo_name, config['base_path'])
def check_int(environ, match_dict):
return match_dict.get('id').isdigit()
# The ErrorController route (handles 404/500 error pages); it should
# likely stay at the top, ensuring it can always be resolved
rmap.connect('/error/{action}', controller='error')
rmap.connect('/error/{action}/{id}', controller='error')
#==========================================================================
# CUSTOM ROUTES HERE
#MAIN PAGE
rmap.connect('home', '/', controller='home', action='index')
rmap.connect('repo_switcher', '/repos', controller='home',
action='repo_switcher')
rmap.connect('bugtracker',
"http://bitbucket.org/marcinkuzminski/rhodecode/issues",
_static=True)
rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
#ADMIN REPOSITORY REST ROUTES
with rmap.submapper(path_prefix=ADMIN_PREFIX,
controller='admin/repos') as m:
m.connect("repos", "/repos",
action="create", conditions=dict(method=["POST"]))
action="index", conditions=dict(method=["GET"]))
m.connect("formatted_repos", "/repos.{format}",
action="index",
conditions=dict(method=["GET"]))
m.connect("new_repo", "/repos/new",
action="new", conditions=dict(method=["GET"]))
m.connect("formatted_new_repo", "/repos/new.{format}",
m.connect("/repos/{repo_name:.*}",
action="update", conditions=dict(method=["PUT"],
function=check_repo))
action="delete", conditions=dict(method=["DELETE"],
m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
action="edit", conditions=dict(method=["GET"],
m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
m.connect("repo", "/repos/{repo_name:.*}",
action="show", conditions=dict(method=["GET"],
m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
#ajax delete repo perm user
m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
action="delete_perm_user", conditions=dict(method=["DELETE"],
#ajax delete repo perm users_group
m.connect('delete_repo_users_group',
"/repos_delete_users_group/{repo_name:.*}",
action="delete_perm_users_group",
conditions=dict(method=["DELETE"], function=check_repo))
#settings actions
m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
action="repo_stats", conditions=dict(method=["DELETE"],
m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
action="repo_cache", conditions=dict(method=["DELETE"],
m.connect('repo_public_journal',
"/repos_public_journal/{repo_name:.*}",
action="repo_public_journal", conditions=dict(method=["PUT"],
m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
action="repo_pull", conditions=dict(method=["PUT"],
controller='admin/repos_groups') as m:
m.connect("repos_groups", "/repos_groups",
m.connect("formatted_repos_groups", "/repos_groups.{format}",
m.connect("new_repos_group", "/repos_groups/new",
m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
m.connect("update_repos_group", "/repos_groups/{id}",
function=check_int))
m.connect("delete_repos_group", "/repos_groups/{id}",
m.connect("edit_repos_group", "/repos_groups/{id}/edit",
m.connect("formatted_edit_repos_group",
"/repos_groups/{id}.{format}/edit",
m.connect("repos_group", "/repos_groups/{id}",
m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
#ADMIN USER REST ROUTES
controller='admin/users') as m:
m.connect("users", "/users",
m.connect("formatted_users", "/users.{format}",
m.connect("new_user", "/users/new",
m.connect("formatted_new_user", "/users/new.{format}",
m.connect("update_user", "/users/{id}",
action="update", conditions=dict(method=["PUT"]))
m.connect("delete_user", "/users/{id}",
action="delete", conditions=dict(method=["DELETE"]))
m.connect("edit_user", "/users/{id}/edit",
action="edit", conditions=dict(method=["GET"]))
m.connect("formatted_edit_user",
"/users/{id}.{format}/edit",
m.connect("user", "/users/{id}",
action="show", conditions=dict(method=["GET"]))
m.connect("formatted_user", "/users/{id}.{format}",
#EXTRAS USER ROUTES
m.connect("user_perm", "/users_perm/{id}",
action="update_perm", conditions=dict(method=["PUT"]))
#ADMIN USERS REST ROUTES
controller='admin/users_groups') as m:
m.connect("users_groups", "/users_groups",
m.connect("formatted_users_groups", "/users_groups.{format}",
m.connect("new_users_group", "/users_groups/new",
m.connect("formatted_new_users_group", "/users_groups/new.{format}",
m.connect("update_users_group", "/users_groups/{id}",
m.connect("delete_users_group", "/users_groups/{id}",
m.connect("edit_users_group", "/users_groups/{id}/edit",
m.connect("formatted_edit_users_group",
"/users_groups/{id}.{format}/edit",
m.connect("users_group", "/users_groups/{id}",
m.connect("formatted_users_group", "/users_groups/{id}.{format}",
m.connect("users_group_perm", "/users_groups_perm/{id}",
#ADMIN GROUP REST ROUTES
rmap.resource('group', 'groups',
controller='admin/groups', path_prefix=ADMIN_PREFIX)
#ADMIN PERMISSIONS REST ROUTES
rmap.resource('permission', 'permissions',
controller='admin/permissions', path_prefix=ADMIN_PREFIX)
##ADMIN LDAP SETTINGS
rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
controller='admin/ldap_settings', action='ldap_settings',
conditions=dict(method=["POST"]))
rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
controller='admin/ldap_settings')
#ADMIN SETTINGS REST ROUTES
controller='admin/settings') as m:
m.connect("admin_settings", "/settings",
m.connect("formatted_admin_settings", "/settings.{format}",
m.connect("admin_new_setting", "/settings/new",
m.connect("formatted_admin_new_setting", "/settings/new.{format}",
m.connect("/settings/{setting_id}",
m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
m.connect("formatted_admin_edit_setting",
"/settings/{setting_id}.{format}/edit",
m.connect("admin_setting", "/settings/{setting_id}",
m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
m.connect("admin_settings_my_account", "/my_account",
action="my_account", conditions=dict(method=["GET"]))
m.connect("admin_settings_my_account_update", "/my_account_update",
action="my_account_update", conditions=dict(method=["PUT"]))
m.connect("admin_settings_create_repository", "/create_repository",
action="create_repository", conditions=dict(method=["GET"]))
#ADMIN MAIN PAGES
controller='admin/admin') as m:
m.connect('admin_home', '', action='index')
m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
action='add_repo')
#USER JOURNAL
rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
controller='journal', action="public_journal")
rmap.connect('public_journal_rss', '%s/public_journal_rss' % ADMIN_PREFIX,
controller='journal', action="public_journal_rss")
rmap.connect('public_journal_atom',
'%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
action="public_journal_atom")
rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
controller='journal', action='toggle_following',
#SEARCH
rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
controller='search')
#LOGIN/LOGOUT/REGISTER/SIGN IN
rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
action='logout')
rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
action='register')
rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
controller='login', action='password_reset')
rmap.connect('reset_password_confirmation',
'%s/password_reset_confirmation' % ADMIN_PREFIX,
controller='login', action='password_reset_confirmation')
#FEEDS
rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
controller='feed', action='rss',
conditions=dict(function=check_repo))
rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
controller='feed', action='atom',
# REPOSITORY ROUTES
rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
controller='changeset', revision='tip',
rmap.connect('raw_changeset_home',
'/{repo_name:.*}/raw-changeset/{revision}',
controller='changeset', action='raw_changeset',
revision='tip', conditions=dict(function=check_repo))
rmap.connect('summary_home', '/{repo_name:.*}',
controller='summary', conditions=dict(function=check_repo))
rmap.connect('summary_home', '/{repo_name:.*}/summary',
rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
controller='shortlog', conditions=dict(function=check_repo))
rmap.connect('branches_home', '/{repo_name:.*}/branches',
controller='branches', conditions=dict(function=check_repo))
rmap.connect('tags_home', '/{repo_name:.*}/tags',
controller='tags', conditions=dict(function=check_repo))
rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
controller='changelog', conditions=dict(function=check_repo))
rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
controller='files', revision='tip', f_path='',
rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
controller='files', action='diff', revision='tip', f_path='',
rmap.connect('files_rawfile_home',
'/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
controller='files', action='rawfile', revision='tip',
f_path='', conditions=dict(function=check_repo))
rmap.connect('files_raw_home',
'/{repo_name:.*}/raw/{revision}/{f_path:.*}',
controller='files', action='raw', revision='tip', f_path='',
rmap.connect('files_annotate_home',
'/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
controller='files', action='annotate', revision='tip',
rmap.connect('files_edit_home',
'/{repo_name:.*}/edit/{revision}/{f_path:.*}',
controller='files', action='edit', revision='tip',
rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
controller='files', action='archivefile',
rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
controller='settings', action="delete",
rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
controller='settings', action="update",
conditions=dict(method=["PUT"], function=check_repo))
rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
controller='settings', action='index',
rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
controller='settings', action='fork_create',
conditions=dict(function=check_repo, method=["POST"]))
rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
controller='settings', action='fork',
rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
controller='followers', action='followers',
rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
controller='forks', action='forks',
return rmap
# -*- coding: utf-8 -*-
rhodecode.controllers.login
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Login controller for rhodeocode
:created_on: Apr 22, 2010
:author: marcink
:copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
:license: GPLv3, see COPYING for more details.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import formencode
from formencode import htmlfill
from pylons.i18n.translation import _
from pylons.controllers.util import abort, redirect
from pylons import request, response, session, tmpl_context as c, url
import rhodecode.lib.helpers as h
from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
from rhodecode.lib.base import BaseController, render
from rhodecode.model.db import User
from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
from rhodecode.model.user import UserModel
log = logging.getLogger(__name__)
class LoginController(BaseController):
def __before__(self):
super(LoginController, self).__before__()
def index(self):
#redirect if already logged in
c.came_from = request.GET.get('came_from', None)
if self.rhodecode_user.is_authenticated \
and self.rhodecode_user.username != 'default':
return redirect(url('home'))
if request.POST:
#import Login Form validator class
login_form = LoginForm()
try:
c.form_result = login_form.to_python(dict(request.POST))
#form checks for username/password, now we're authenticated
username = c.form_result['username']
user = User.by_username(username,
case_insensitive=True)
auth_user = AuthUser(user.user_id)
auth_user.set_authenticated()
session['rhodecode_user'] = auth_user
session.save()
log.info('user %s is now authenticated and stored in session',
username)
user.update_lastlogin()
if c.came_from:
return redirect(c.came_from)
else:
except formencode.Invalid, errors:
return htmlfill.render(
render('/login.html'),
defaults=errors.value,
errors=errors.error_dict or {},
prefix_error=False,
encoding="UTF-8")
return render('/login.html')
@HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
'hg.register.manual_activate')
def register(self):
user_model = UserModel()
c.auto_active = False
for perm in user_model.get_by_username('default',
cache=False).user_perms:
if perm.permission.permission_name == 'hg.register.auto_activate':
c.auto_active = True
break
register_form = RegisterForm()()
form_result = register_form.to_python(dict(request.POST))
form_result['active'] = c.auto_active
user_model.create_registration(form_result)
h.flash(_('You have successfully registered into rhodecode'),
category='success')
return redirect(url('login_home'))
render('/register.html'),
return render('/register.html')
def password_reset(self):
password_reset_form = PasswordResetForm()()
form_result = password_reset_form.to_python(dict(request.POST))
user_model.reset_password(form_result)
h.flash(_('Your new password was sent'),
user_model.reset_password_link(form_result)
h.flash(_('Your password reset link was sent'),
render('/password_reset.html'),
return render('/password_reset.html')
def password_reset_confirmation(self):
if request.GET and request.GET.get('key'):
user = User.get_by_api_key(request.GET.get('key'))
data = dict(email=user.email)
user_model.reset_password(data)
h.flash(_('Your password reset was successful, '
'new password has been sent to your email'),
except Exception, e:
log.error(e)
return redirect(url('reset_password'))
def logout(self):
del session['rhodecode_user']
log.info('Logging out and setting user as Empty')
redirect(url('home'))
rhodecode.lib.celerylib.tasks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
RhodeCode task modules, containing all task that suppose to be run
by celery daemon
:created_on: Oct 6, 2010
from celery.decorators import task
import os
import traceback
from os.path import dirname as dn, join as jn
from time import mktime
from operator import itemgetter
from string import lower
from pylons import config
from pylons import config, url
from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
__get_lockkey, LockHeld, DaemonLock
from rhodecode.lib.helpers import person
from rhodecode.lib.smtp_mailer import SmtpMailer
from rhodecode.lib.utils import add_cache
from rhodecode.lib.odict import OrderedDict
from rhodecode.model import init_model
from rhodecode.model import meta
from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
from vcs.backends import get_repo
from sqlalchemy import engine_from_config
add_cache(config)
import json
except ImportError:
#python 2.5 compatibility
import simplejson as json
__all__ = ['whoosh_index', 'get_commits_stats',
'reset_user_password', 'send_email']
CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
def get_session():
if CELERY_ON:
engine = engine_from_config(config, 'sqlalchemy.db1.')
init_model(engine)
sa = meta.Session()
return sa
def get_repos_path():
sa = get_session()
q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
return q.ui_value
@task(ignore_result=True)
@locked_task
def whoosh_index(repo_location, full_index):
#log = whoosh_index.get_logger()
from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
index_location = config['index_dir']
WhooshIndexingDaemon(index_location=index_location,
repo_location=repo_location, sa=get_session())\
.run(full_index=full_index)
def get_commits_stats(repo_name, ts_min_y, ts_max_y):
log = get_commits_stats.get_logger()
except:
lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
ts_max_y)
lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
log.info('running task with lockkey %s', lockkey)
lock = l = DaemonLock(jn(lockkey_path, lockkey))
#for js data compatibilty cleans the key for person from '
akc = lambda k: person(k).replace('"', "")
co_day_auth_aggr = {}
commits_by_day_aggregate = {}
repos_path = get_repos_path()
repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
repo_size = len(repo.revisions)
#return if repo have no revisions
if repo_size < 1:
lock.release()
return True
skip_date_limit = True
parse_limit = int(config['app_conf'].get('commit_parse_limit'))
last_rev = 0
last_cs = None
timegetter = itemgetter('time')
dbrepo = sa.query(Repository)\
.filter(Repository.repo_name == repo_name).scalar()
cur_stats = sa.query(Statistics)\
.filter(Statistics.repository == dbrepo).scalar()
if cur_stats is not None:
last_rev = cur_stats.stat_on_revision
if last_rev == repo.get_changeset().revision and repo_size > 1:
#pass silently without any work if we're not on first revision or
#current state of parsing revision(from db marker) is the
#last revision
if cur_stats:
commits_by_day_aggregate = OrderedDict(json.loads(
cur_stats.commit_activity_combined))
co_day_auth_aggr = json.loads(cur_stats.commit_activity)
log.debug('starting parsing %s', parse_limit)
lmktime = mktime
last_rev = last_rev + 1 if last_rev > 0 else last_rev
for cs in repo[last_rev:last_rev + parse_limit]:
last_cs = cs # remember last parsed changeset
k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
if akc(cs.author) in co_day_auth_aggr:
l = [timegetter(x) for x in
co_day_auth_aggr[akc(cs.author)]['data']]
time_pos = l.index(k)
except ValueError:
time_pos = False
if time_pos >= 0 and time_pos is not False:
datadict = \
co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
datadict["commits"] += 1
datadict["added"] += len(cs.added)
datadict["changed"] += len(cs.changed)
datadict["removed"] += len(cs.removed)
if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
datadict = {"time": k,
"commits": 1,
"added": len(cs.added),
"changed": len(cs.changed),
"removed": len(cs.removed),
}
co_day_auth_aggr[akc(cs.author)]['data']\
.append(datadict)
co_day_auth_aggr[akc(cs.author)] = {
"label": akc(cs.author),
"data": [{"time":k,
"commits":1,
"added":len(cs.added),
"changed":len(cs.changed),
"removed":len(cs.removed),
}],
"schema": ["commits"],
#gather all data by day
if k in commits_by_day_aggregate:
commits_by_day_aggregate[k] += 1
commits_by_day_aggregate[k] = 1
overview_data = sorted(commits_by_day_aggregate.items(),
key=itemgetter(0))
if not co_day_auth_aggr:
co_day_auth_aggr[akc(repo.contact)] = {
"label": akc(repo.contact),
"data": [0, 1],
stats = cur_stats if cur_stats else Statistics()
stats.commit_activity = json.dumps(co_day_auth_aggr)
stats.commit_activity_combined = json.dumps(overview_data)
log.debug('last revison %s', last_rev)
leftovers = len(repo.revisions[last_rev:])
log.debug('revisions to parse %s', leftovers)
if last_rev == 0 or leftovers < parse_limit:
log.debug('getting code trending stats')
stats.languages = json.dumps(__get_codes_stats(repo_name))
stats.repository = dbrepo
stats.stat_on_revision = last_cs.revision if last_cs else 0
sa.add(stats)
sa.commit()
log.error(traceback.format_exc())
sa.rollback()
return False
#final release
#execute another task if celery is enabled
if len(repo.revisions) > 1 and CELERY_ON:
run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
except LockHeld:
log.info('LockHeld')
return 'Task with key %s already running' % lockkey
def send_password_link(user_email):
log = reset_user_password.get_logger()
from rhodecode.lib import auth
user = sa.query(User).filter(User.email == user_email).scalar()
if user:
link = url('reset_password_confirmation', key=user.api_key,
qualified=True)
tmpl = """
Hello %s
We received a request to create a new password for your account.
You can generate it by clicking following URL:
%s
If you didn't request new password please ignore this email.
run_task(send_email, user_email,
"RhodeCode password reset link",
tmpl % (user.short_contact, link))
log.info('send new password mail to %s', user_email)
log.error('Failed to update user password')
def reset_user_password(user_email):
new_passwd = auth.PasswordGenerator().gen_password(8,
auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
user.password = auth.get_crypt_password(new_passwd)
user.api_key = auth.generate_api_key(user.username)
sa.add(user)
log.info('change password for %s', user_email)
if new_passwd is None:
raise Exception('unable to generate new password')
"Your new rhodecode password",
'Your new rhodecode password:%s' % (new_passwd))
"Your new RhodeCode password",
'Your new RhodeCode password:%s' % (new_passwd))
def send_email(recipients, subject, body):
Sends an email with defined parameters from the .ini files.
:param recipients: list of recipients, it this is empty the defined email
address from field 'email_to' is used instead
:param subject: subject of the mail
:param body: body of the mail
log = send_email.get_logger()
email_config = config
if not recipients:
recipients = [email_config.get('email_to')]
mail_from = email_config.get('app_email_from')
user = email_config.get('smtp_username')
passwd = email_config.get('smtp_password')
mail_server = email_config.get('smtp_server')
mail_port = email_config.get('smtp_port')
tls = str2bool(email_config.get('smtp_use_tls'))
ssl = str2bool(email_config.get('smtp_use_ssl'))
debug = str2bool(config.get('debug'))
m = SmtpMailer(mail_from, user, passwd, mail_server,
mail_port, ssl, tls, debug=debug)
m.send(recipients, subject, body)
log.error('Mail sending failed')
def create_repo_fork(form_data, cur_user):
from rhodecode.model.repo import RepoModel
from vcs import get_backend
log = create_repo_fork.get_logger()
repo_model = RepoModel(get_session())
repo_model.create(form_data, cur_user, just_db=True, fork=True)
repo_name = form_data['repo_name']
repo_path = os.path.join(repos_path, repo_name)
repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
alias = form_data['repo_type']
log.info('creating repo fork %s as %s', repo_name, repo_path)
backend = get_backend(alias)
backend(str(repo_fork_path), create=True, src_url=str(repo_path))
def __get_codes_stats(repo_name):
tip = repo.get_changeset()
code_stats = {}
def aggregate(cs):
for f in cs[2]:
ext = lower(f.extension)
if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
if ext in code_stats:
code_stats[ext] += 1
code_stats[ext] = 1
map(aggregate, tip.walk('/'))
return code_stats or {}
rhodecode.lib.smtp_mailer
~~~~~~~~~~~~~~~~~~~~~~~~~
Simple smtp mailer used in RhodeCode
:created_on: Sep 13, 2010
:copyright: (c) 2011 by marcink.
:license: LICENSE_NAME, see LICENSE_FILE for more details.
import smtplib
import mimetypes
from socket import sslerror
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import formatdate
from email import encoders
class SmtpMailer(object):
"""SMTP mailer class
mailer = SmtpMailer(mail_from, user, passwd, mail_server,
mail_port, ssl, tls)
mailer.send(recipients, subject, body, attachment_files)
:param recipients might be a list of string or single string
:param attachment_files is a dict of {filename:location}
it tries to guess the mimetype and attach the file
def __init__(self, mail_from, user, passwd, mail_server,
mail_port=None, ssl=False, tls=False, debug=False):
self.mail_from = mail_from
self.mail_server = mail_server
self.mail_port = mail_port
self.user = user
self.passwd = passwd
self.ssl = ssl
self.tls = tls
self.debug = debug
def send(self, recipients=[], subject='', body='', attachment_files=None):
if isinstance(recipients, basestring):
recipients = [recipients]
if self.ssl:
smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
if self.tls:
smtp_serv.ehlo()
smtp_serv.starttls()
if self.debug:
smtp_serv.set_debuglevel(1)
#if server requires authorization you must provide login and password
#but only if we have them
if self.user and self.passwd:
smtp_serv.login(self.user, self.passwd)
date_ = formatdate(localtime=True)
msg = MIMEMultipart()
msg.set_type('multipart/alternative')
msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
text_msg = MIMEText(body)
text_msg.set_type('text/plain')
text_msg.set_param('charset', 'UTF-8')
msg['From'] = self.mail_from
msg['To'] = ','.join(recipients)
msg['Date'] = date_
msg['Subject'] = subject
msg.attach(MIMEText(body))
msg.attach(text_msg)
if attachment_files:
self.__atach_files(msg, attachment_files)
smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
logging.info('MAIL SEND TO: %s' % recipients)
smtp_serv.quit()
except sslerror:
# sslerror is raised in tls connections on closing sometimes
pass
def __atach_files(self, msg, attachment_files):
if isinstance(attachment_files, dict):
for f_name, msg_file in attachment_files.items():
ctype, encoding = mimetypes.guess_type(f_name)
logging.info("guessing file %s type based on %s", ctype,
f_name)
if ctype is None or encoding is not None:
# No guess could be made, or the file is encoded
# (compressed), so use a generic bag-of-bits type.
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
if maintype == 'text':
# Note: we should handle calculating the charset
file_part = MIMEText(self.get_content(msg_file),
_subtype=subtype)
elif maintype == 'image':
file_part = MIMEImage(self.get_content(msg_file),
elif maintype == 'audio':
file_part = MIMEAudio(self.get_content(msg_file),
file_part = MIMEBase(maintype, subtype)
file_part.set_payload(self.get_content(msg_file))
# Encode the payload using Base64
encoders.encode_base64(msg)
# Set the filename parameter
file_part.add_header('Content-Disposition', 'attachment',
filename=f_name)
file_part.add_header('Content-Type', ctype, name=f_name)
msg.attach(file_part)
raise Exception('Attachment files should be'
'a dict in format {"filename":"filepath"}')
def get_content(self, msg_file):
"""Get content based on type, if content is a string do open first
else just read because it's a probably open file object
:param msg_file:
if isinstance(msg_file, str):
return open(msg_file, "rb").read()
#just for safe seek to 0
msg_file.seek(0)
return msg_file.read()
rhodecode.model.db
~~~~~~~~~~~~~~~~~~
Database Models for RhodeCode
:created_on: Apr 08, 2010
import datetime
from datetime import date
from sqlalchemy import *
from sqlalchemy.exc import DatabaseError
from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
from sqlalchemy.orm.interfaces import MapperExtension
from beaker.cache import cache_region, region_invalidate
from vcs.utils.helpers import get_scm
from vcs.exceptions import RepositoryError, VCSError
from vcs.utils.lazy import LazyProperty
from vcs.nodes import FileNode
from rhodecode.lib import str2bool, json, safe_str
from rhodecode.model.meta import Base, Session
from rhodecode.model.caching_query import FromCache
#==============================================================================
# BASE CLASSES
class ModelSerializer(json.JSONEncoder):
Simple Serializer for JSON,
usage::
to make object customized for serialization implement a __json__
method that will return a dict for serialization into json
example::
class Task(object):
def __init__(self, name, value):
self.name = name
self.value = value
def __json__(self):
return dict(name=self.name,
value=self.value)
def default(self, obj):
if hasattr(obj, '__json__'):
return obj.__json__()
return json.JSONEncoder.default(self, obj)
class BaseModel(object):
"""Base Model for all classess
@classmethod
def _get_keys(cls):
"""return column names for this model """
return class_mapper(cls).c.keys()
def get_dict(self):
"""return dict with keys and values corresponding
to this model data """
d = {}
for k in self._get_keys():
d[k] = getattr(self, k)
return d
def get_appstruct(self):
"""return list with keys and values tupples corresponding
l = []
l.append((k, getattr(self, k),))
return l
def populate_obj(self, populate_dict):
"""populate model with data from given populate_dict"""
if k in populate_dict:
setattr(self, k, populate_dict[k])
def query(cls):
return Session.query(cls)
def get(cls, id_):
return Session.query(cls).get(id_)
class RhodeCodeSettings(Base, BaseModel):
__tablename__ = 'rhodecode_settings'
__table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
def __init__(self, k='', v=''):
self.app_settings_name = k
self.app_settings_value = v
def __repr__(self):
return "<%s('%s:%s')>" % (self.__class__.__name__,
self.app_settings_name, self.app_settings_value)
def get_by_name(cls, ldap_key):
return Session.query(cls)\
.filter(cls.app_settings_name == ldap_key).scalar()
def get_app_settings(cls, cache=False):
ret = Session.query(cls)
if cache:
ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
if not ret:
raise Exception('Could not get application settings !')
settings = {}
for each in ret:
settings['rhodecode_' + each.app_settings_name] = \
each.app_settings_value
return settings
def get_ldap_settings(cls, cache=False):
ret = Session.query(cls)\
.filter(cls.app_settings_name.startswith('ldap_'))\
.all()
fd = {}
for row in ret:
fd.update({row.app_settings_name:row.app_settings_value})
fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
return fd
class RhodeCodeUi(Base, BaseModel):
__tablename__ = 'rhodecode_ui'
__table_args__ = {'extend_existing':True}
ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
def get_by_key(cls, key):
return Session.query(cls).filter(cls.ui_key == key)
class User(Base, BaseModel):
__tablename__ = 'users'
__table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
active = Column("active", Boolean(), nullable=True, unique=None, default=None)
admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
user_log = relationship('UserLog', cascade='all')
user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
repositories = relationship('Repository')
user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
group_member = relationship('UsersGroupMember', cascade='all')
@property
def full_contact(self):
return '%s %s <%s>' % (self.name, self.lastname, self.email)
def short_contact(self):
return '%s %s' % (self.name, self.lastname)
def is_admin(self):
return self.admin
return "<%s('id:%s:%s')>" % (self.__class__.__name__,
self.user_id, self.username)
return self.__class__.__name__
def by_username(cls, username, case_insensitive=False):
if case_insensitive:
return Session.query(cls).filter(cls.username.like(username)).one()
return Session.query(cls).filter(cls.username == username).one()
def get_by_api_key(cls, api_key):
return Session.query(cls).filter(cls.api_key == api_key).one()
def update_lastlogin(self):
"""Update user lastlogin"""
self.last_login = datetime.datetime.now()
Session.add(self)
Session.commit()
log.debug('updated user %s lastlogin', self.username)
class UserLog(Base, BaseModel):
__tablename__ = 'user_logs'
user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
def action_as_day(self):
return date(*self.action_date.timetuple()[:3])
user = relationship('User')
repository = relationship('Repository')
class UsersGroup(Base, BaseModel):
__tablename__ = 'users_groups'
users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
gr = Session.query(cls)\
.filter(cls.users_group_name.ilike(group_name))
gr = Session.query(UsersGroup)\
.filter(UsersGroup.users_group_name == group_name)
gr = gr.options(FromCache("sql_cache_short",
"get_user_%s" % group_name))
return gr.scalar()
class UsersGroupMember(Base, BaseModel):
__tablename__ = 'users_groups_members'
users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
user = relationship('User', lazy='joined')
users_group = relationship('UsersGroup')
def __init__(self, gr_id='', u_id=''):
self.users_group_id = gr_id
self.user_id = u_id
class Repository(Base, BaseModel):
__tablename__ = 'repositories'
__table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
private = Column("private", Boolean(), nullable=True, unique=None, default=None)
enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
fork = relationship('Repository', remote_side=repo_id)
group = relationship('Group')
repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
stats = relationship('Statistics', cascade='all', uselist=False)
followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
logs = relationship('UserLog', cascade='all')
self.repo_id, self.repo_name)
def by_repo_name(cls, repo_name):
q = Session.query(cls).filter(cls.repo_name == repo_name)
q = q.options(joinedload(Repository.fork))\
.options(joinedload(Repository.user))\
.options(joinedload(Repository.group))\
return q.one()
def get_repo_forks(cls, repo_id):
return Session.query(cls).filter(Repository.fork_id == repo_id)
def just_name(self):
return self.repo_name.split(os.sep)[-1]
def groups_with_parents(self):
groups = []
if self.group is None:
return groups
cur_gr = self.group
groups.insert(0, cur_gr)
while 1:
gr = getattr(cur_gr, 'parent_group', None)
cur_gr = cur_gr.parent_group
if gr is None:
groups.insert(0, gr)
def groups_and_repo(self):
return self.groups_with_parents, self.just_name
@LazyProperty
def repo_path(self):
Returns base full path for that repository means where it actually
exists on a filesystem
q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
q.options(FromCache("sql_cache_short", "repository_repo_path"))
return q.one().ui_value
def repo_full_path(self):
p = [self.repo_path]
# we need to split the name by / since this is how we store the
# names in the database, but that eventually needs to be converted
# into a valid system path
p += self.repo_name.split('/')
return os.path.join(*p)
def _ui(self):
Creates an db based ui object for this repository
from mercurial import ui
from mercurial import config
baseui = ui.ui()
#clean the baseui object
baseui._ocfg = config.config()
baseui._ucfg = config.config()
baseui._tcfg = config.config()
ret = Session.query(RhodeCodeUi)\
.options(FromCache("sql_cache_short",
"repository_repo_ui")).all()
hg_ui = ret
for ui_ in hg_ui:
if ui_.ui_active:
log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
ui_.ui_key, ui_.ui_value)
baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
return baseui
# SCM CACHE INSTANCE
def invalidate(self):
Returns Invalidation object if this repo should be invalidated
None otherwise. `cache_active = False` means that this cache
state is not valid and needs to be invalidated
return Session.query(CacheInvalidation)\
.filter(CacheInvalidation.cache_key == self.repo_name)\
.filter(CacheInvalidation.cache_active == False)\
.scalar()
def set_invalidate(self):
set a cache for invalidation for this instance
inv = Session.query(CacheInvalidation)\
if inv is None:
inv = CacheInvalidation(self.repo_name)
inv.cache_active = True
Session.add(inv)
def scm_instance(self):
return self.__get_instance()
def scm_instance_cached(self):
@cache_region('long_term')
def _c(repo_name):
inv = self.invalidate
if inv:
region_invalidate(_c, None, self.repo_name)
#update our cache
inv.cache_key.cache_active = True
# TODO: remove this trick when beaker 1.6 is released
# and have fixed this issue
rn = safe_str(self.repo_name)
return _c(rn)
def __get_instance(self):
repo_full_path = self.repo_full_path
alias = get_scm(repo_full_path)[0]
log.debug('Creating instance of %s repository', alias)
except VCSError:
log.error('Perhaps this repository is in db and not in '
'filesystem run rescan repositories with '
'"destroy old data " option from admin panel')
return
if alias == 'hg':
repo = backend(safe_str(repo_full_path), create=False,
baseui=self._ui)
#skip hidden web repository
if repo._get_hidden():
repo = backend(repo_full_path, create=False)
return repo
class Group(Base, BaseModel):
__tablename__ = 'groups'
__table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
__mapper_args__ = {'order_by':'group_name'}
group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
parent_group = relationship('Group', remote_side=group_id)
def __init__(self, group_name='', parent_group=None):
self.group_name = group_name
self.parent_group = parent_group
return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
self.group_name)
def url_sep(cls):
return '/'
def parents(self):
parents_recursion_limit = 5
if self.parent_group is None:
cur_gr = self.parent_group
cnt = 0
cnt += 1
if cnt == parents_recursion_limit:
# this will prevent accidental infinit loops
log.error('group nested more than %s' %
parents_recursion_limit)
def children(self):
return Session.query(Group).filter(Group.parent_group == self)
def full_path(self):
return Group.url_sep().join([g.group_name for g in self.parents] +
[self.group_name])
def repositories(self):
return Session.query(Repository).filter(Repository.group == self)
def repositories_recursive_count(self):
cnt = self.repositories.count()
def children_count(group):
for child in group.children:
cnt += child.repositories.count()
cnt += children_count(child)
return cnt
return cnt + children_count(self)
class Permission(Base, BaseModel):
__tablename__ = 'permissions'
permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
self.permission_id, self.permission_name)
return Session.query(cls).filter(cls.permission_name == key).scalar()
class RepoToPerm(Base, BaseModel):
__tablename__ = 'repo_to_perm'
__table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
permission = relationship('Permission')
class UserToPerm(Base, BaseModel):
__tablename__ = 'user_to_perm'
__table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
rhodecode.model.user
~~~~~~~~~~~~~~~~~~~~
users model for RhodeCode
:created_on: Apr 9, 2010
from rhodecode.model import BaseModel
from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
from rhodecode.lib.exceptions import DefaultUserException, \
UserOwnsReposException
from rhodecode.lib import generate_api_key
from sqlalchemy.orm import joinedload
PERM_WEIGHTS = {'repository.none': 0,
'repository.read': 1,
'repository.write': 3,
'repository.admin': 3}
class UserModel(BaseModel):
def get(self, user_id, cache=False):
user = self.sa.query(User)
user = user.options(FromCache("sql_cache_short",
"get_user_%s" % user_id))
return user.get(user_id)
def get_by_username(self, username, cache=False, case_insensitive=False):
user = self.sa.query(User).filter(User.username.ilike(username))
user = self.sa.query(User)\
.filter(User.username == username)
"get_user_%s" % username))
return user.scalar()
def get_by_api_key(self, api_key, cache=False):
.filter(User.api_key == api_key)
"get_user_%s" % api_key))
def create(self, form_data):
new_user = User()
for k, v in form_data.items():
setattr(new_user, k, v)
new_user.api_key = generate_api_key(form_data['username'])
self.sa.add(new_user)
self.sa.commit()
self.sa.rollback()
raise
def create_ldap(self, username, password, user_dn, attrs):
Checks if user is in database, if not creates this user marked
as ldap user
:param username:
:param password:
:param user_dn:
:param attrs:
from rhodecode.lib.auth import get_crypt_password
log.debug('Checking for such ldap account in RhodeCode database')
if self.get_by_username(username, case_insensitive=True) is None:
# add ldap account always lowercase
new_user.username = username.lower()
new_user.password = get_crypt_password(password)
new_user.api_key = generate_api_key(username)
new_user.email = attrs['email']
new_user.active = True
new_user.ldap_dn = user_dn
new_user.name = attrs['name']
new_user.lastname = attrs['lastname']
except (DatabaseError,):
log.debug('this %s user exists skipping creation of ldap account',
def create_registration(self, form_data):
from rhodecode.lib.celerylib import tasks, run_task
if k != 'admin':
body = ('New user registration\n'
'username: %s\n'
'email: %s\n')
body = body % (form_data['username'], form_data['email'])
run_task(tasks.send_email, None,
_('[RhodeCode] New User registration'),
body)
def update(self, user_id, form_data):
user = self.get(user_id, cache=False)
if user.username == 'default':
raise DefaultUserException(
_("You can't Edit this user since it's"
" crucial for entire application"))
if k == 'new_password' and v != '':
user.password = v
user.api_key = generate_api_key(user.username)
setattr(user, k, v)
self.sa.add(user)
def update_my_account(self, user_id, form_data):
if k not in ['admin', 'active']:
def delete(self, user_id):
_("You can't remove this user since it's"
if user.repositories:
raise UserOwnsReposException(_('This user still owns %s '
'repositories and cannot be '
'removed. Switch owners or '
'remove those repositories') \
% user.repositories)
self.sa.delete(user)
def reset_password_link(self, data):
run_task(tasks.send_password_link, data['email'])
def reset_password(self, data):
run_task(tasks.reset_user_password, data['email'])
def fill_data(self, auth_user, user_id=None, api_key=None):
Fetches auth_user by user_id,or api_key if present.
Fills auth_user attributes with those taken from database.
Additionally set's is_authenitated if lookup fails
present in database
:param auth_user: instance of user to set attributes
:param user_id: user id to fetch by
:param api_key: api key to fetch by
if user_id is None and api_key is None:
raise Exception('You need to pass user_id or api_key')
if api_key:
dbuser = self.get_by_api_key(api_key)
dbuser = self.get(user_id)
if dbuser is not None:
log.debug('filling %s data', dbuser)
for k, v in dbuser.get_dict().items():
setattr(auth_user, k, v)
auth_user.is_authenticated = False
return auth_user
def fill_perms(self, user):
Fills user permission attribute with permissions taken from database
works for permissions given for repositories, and for permissions that
are granted to groups
:param user: user instance to fill his perms
user.permissions['repositories'] = {}
user.permissions['global'] = set()
#======================================================================
# fetch default permissions
default_user = self.get_by_username('default', cache=True)
default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
.join((Repository, RepoToPerm.repository_id ==
Repository.repo_id))\
.join((Permission, RepoToPerm.permission_id ==
Permission.permission_id))\
.filter(RepoToPerm.user == default_user).all()
if user.is_admin:
#==================================================================
# #admin have all default rights set to admin
user.permissions['global'].add('hg.admin')
for perm in default_perms:
p = 'repository.admin'
user.permissions['repositories'][perm.RepoToPerm.
repository.repo_name] = p
# set default permissions
uid = user.user_id
#default global
default_global_perms = self.sa.query(UserToPerm)\
.filter(UserToPerm.user == default_user)
for perm in default_global_perms:
user.permissions['global'].add(perm.permission.permission_name)
#default for repositories
if perm.Repository.private and not (perm.Repository.user_id ==
uid):
#diself.sable defaults for private repos,
p = 'repository.none'
elif perm.Repository.user_id == uid:
#set admin if owner
p = perm.Permission.permission_name
# overwrite default with user permissions if any
#user global
user_perms = self.sa.query(UserToPerm)\
.options(joinedload(UserToPerm.permission))\
.filter(UserToPerm.user_id == uid).all()
for perm in user_perms:
user.permissions['global'].add(perm.permission.
permission_name)
#user repositories
user_repo_perms = self.sa.query(RepoToPerm, Permission,
Repository)\
.filter(RepoToPerm.user_id == uid).all()
for perm in user_repo_perms:
# set admin if owner
if perm.Repository.user_id == uid:
# check if user is part of groups for this repository and fill in
# (or replace with higher) permissions
#users group global
user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
.options(joinedload(UsersGroupToPerm.permission))\
.join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
UsersGroupMember.users_group_id))\
.filter(UsersGroupMember.user_id == uid).all()
for perm in user_perms_from_users_groups:
#users group repositories
user_repo_perms_from_users_groups = self.sa.query(
UsersGroupRepoToPerm,
Permission, Repository,)\
.join((Repository, UsersGroupRepoToPerm.repository_id ==
.join((Permission, UsersGroupRepoToPerm.permission_id ==
.join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
for perm in user_repo_perms_from_users_groups:
cur_perm = user.permissions['repositories'][perm.
UsersGroupRepoToPerm.
repository.repo_name]
#overwrite permission only if it's greater than permission
# given from other sources
if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
user.permissions['repositories'][perm.UsersGroupRepoToPerm.
return user
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
border:0;
outline:0;
font-size:100%;
vertical-align:baseline;
background:transparent;
margin:0;
padding:0;
body {
line-height:1;
height:100%;
background:url("../images/background.png") repeat scroll 0 0 #B0B0B0;
font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
font-size:12px;
color:#000;
ol,ul {
list-style:none;
blockquote,q {
quotes:none;
blockquote:before,blockquote:after,q:before,q:after {
content:none;
:focus {
del {
text-decoration:line-through;
table {
border-collapse:collapse;
border-spacing:0;
html {
a {
color:#003367;
text-decoration:none;
cursor:pointer;
font-weight:700;
a:hover {
color:#316293;
text-decoration:underline;
h1,h2,h3,h4,h5,h6 {
color:#292929;
h1 {
font-size:22px;
h2 {
font-size:20px;
h3 {
font-size:18px;
h4 {
font-size:16px;
h5 {
font-size:14px;
h6 {
font-size:11px;
ul.circle {
list-style-type:circle;
ul.disc {
list-style-type:disc;
ul.square {
list-style-type:square;
ol.lower-roman {
list-style-type:lower-roman;
ol.upper-roman {
list-style-type:upper-roman;
ol.lower-alpha {
list-style-type:lower-alpha;
ol.upper-alpha {
list-style-type:upper-alpha;
ol.decimal {
list-style-type:decimal;
div.color {
clear:both;
overflow:hidden;
position:absolute;
background:#FFF;
margin:7px 0 0 60px;
padding:1px 1px 1px 0;
div.color a {
width:15px;
height:15px;
display:block;
float:left;
margin:0 0 0 1px;
div.options {
margin:7px 0 0 162px;
div.options a {
height:1%;
padding:3px 8px;
.top-left-rounded-corner {
-webkit-border-top-left-radius: 8px;
-khtml-border-radius-topleft: 8px;
-moz-border-radius-topleft: 8px;
border-top-left-radius: 8px;
.top-right-rounded-corner {
-webkit-border-top-right-radius: 8px;
-khtml-border-radius-topright: 8px;
-moz-border-radius-topright: 8px;
border-top-right-radius: 8px;
.bottom-left-rounded-corner {
-webkit-border-bottom-left-radius: 8px;
-khtml-border-radius-bottomleft: 8px;
-moz-border-radius-bottomleft: 8px;
border-bottom-left-radius: 8px;
.bottom-right-rounded-corner {
-webkit-border-bottom-right-radius: 8px;
-khtml-border-radius-bottomright: 8px;
-moz-border-radius-bottomright: 8px;
border-bottom-right-radius: 8px;
#header {
padding:0 10px;
#header ul#logged-user{
margin-bottom:5px !important;
-webkit-border-radius: 0px 0px 8px 8px;
-khtml-border-radius: 0px 0px 8px 8px;
-moz-border-radius: 0px 0px 8px 8px;
border-radius: 0px 0px 8px 8px;
height:37px;
background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367
background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
#header ul#logged-user li {
margin:8px 0 0;
padding:4px 12px;
border-left: 1px solid #316293;
#header ul#logged-user li.first {
border-left:none;
margin:4px;
#header ul#logged-user li.first div.gravatar {
margin-top:-2px;
#header ul#logged-user li.first div.account {
padding-top:4px;
#header ul#logged-user li.last {
border-right:none;
#header ul#logged-user li a {
color:#fff;
#header ul#logged-user li a:hover {
#header ul#logged-user li.highlight a {
#header ul#logged-user li.highlight a:hover {
color:#FFF;
#header #header-inner {
height:40px;
position:relative;
background:#003367 url("../images/header_inner.png") repeat-x;
border-bottom:2px solid #fff;
#header #header-inner #home a {
width:46px;
background:url("../images/button_home.png");
background-position:0 0;
#header #header-inner #home a:hover {
background-position:0 -40px;
#header #header-inner #logo h1 {
margin:10px 0 0 13px;
#header #header-inner #logo a {
#header #header-inner #logo a:hover {
color:#bfe3ff;
#header #header-inner #quick,#header #header-inner #quick ul {
float:right;
list-style-type:none;
list-style-position:outside;
margin:10px 5px 0 0;
#header #header-inner #quick li {
margin:0 5px 0 0;
#header #header-inner #quick li a {
top:0;
left:0;
background:#369 url("../images/quick_l.png") no-repeat top left;
#header #header-inner #quick li span.short {
padding:9px 6px 8px 6px;
#header #header-inner #quick li span {
right:0;
background:url("../images/quick_r.png") no-repeat top right;
border-left:1px solid #3f6f9f;
padding:10px 12px 8px 10px;
#header #header-inner #quick li span.normal {
border:none;
padding:10px 12px 8px;
#header #header-inner #quick li span.icon {
background:url("../images/quick_l.png") no-repeat top left;
border-right:1px solid #2e5c89;
padding:8px 8px 4px;
#header #header-inner #quick li span.icon_short {
padding:9px 4px 4px;
#header #header-inner #quick li a:hover {
background:#4e4e4e url("../images/quick_l_selected.png") no-repeat top left;
#header #header-inner #quick li a:hover span {
border-left:1px solid #545454;
background:url("../images/quick_r_selected.png") no-repeat top right;
#header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short {
border-right:1px solid #464646;
background:url("../images/quick_l_selected.png") no-repeat top left;
#header #header-inner #quick ul {
top:29px;
min-width:200px;
display:none;
border:1px solid #666;
border-top:1px solid #003367;
z-index:100;
#header #header-inner #quick ul.repo_switcher {
max-height:275px;
overflow-x:hidden;
overflow-y:auto;
#header #header-inner #quick ul.repo_switcher li.qfilter_rs {
float:none;
border-bottom:2px solid #003367;
#header #header-inner #quick .repo_switcher_type{
top:9px;
#header #header-inner #quick li ul li {
border-bottom:1px solid #ddd;
#header #header-inner #quick li ul li a {
width:182px;
height:auto;
font-weight:400;
padding:7px 9px;
#header #header-inner #quick li ul li a:hover {
#header #header-inner #quick ul ul {
top:auto;
#header #header-inner #quick li ul ul {
right:200px;
overflow:auto;
white-space:normal;
#header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover {
background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFF;
width:167px;
padding:12px 9px 7px 24px;
#header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover {
background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFF;
min-width:167px;
#header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover {
background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFF;
#header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover {
background:url("../images/icons/hgicon.png") no-repeat scroll 4px 9px #FFF;
margin:0 0 0 14px;
#header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover {
background:url("../images/icons/giticon.png") no-repeat scroll 4px 9px #FFF;
#header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover {
background:url("../images/icons/database_edit.png") no-repeat scroll 4px 9px #FFF;
#header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover {
background:url("../images/icons/database_link.png") no-repeat scroll 4px 9px #FFF;
#header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover {
background:#FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover {
background:#FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover {
background:#FFF url("../images/icons/cog.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover {
background:#FFF url("../images/icons/key.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover {
background:#FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover {
background:#FFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover {
background:#FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover {
background:#FFF url("../images/icons/delete.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover {
background:#FFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover {
background:#FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.admin,#header #header-inner #quick li ul li a.admin:hover {
background:#FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
#content #left {
width:280px;
#content #right {
margin:0 60px 10px 290px;
#content div.box {
background:#fff;
margin:0 0 10px;
padding:0 0 10px;
#content div.box-left {
width:49%;
clear:none;
@@ -1002,768 +1003,775 @@ border-right:1px solid #2b7089;
border-bottom:1px solid #1a6480;
padding:6px 12px;
div.form div.fields div.field div.highlight .ui-state-hover {
background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
border-top:1px solid #78acbf;
border-left:1px solid #34819e;
border-right:1px solid #35829f;
border-bottom:1px solid #257897;
#content div.box div.form div.fields div.buttons div.highlight input.ui-button {
background:#4e85bb url("../images/button_highlight.png") repeat-x;
border-top:1px solid #5c91a4;
border-left:1px solid #2a6f89;
border-right:1px solid #2b7089;
#content div.box div.form div.fields div.buttons div.highlight input.ui-state-hover {
#content div.box table {
width:100%;
#content div.box table th {
background:#eee;
padding:5px 0px 5px 5px;
#content div.box table th.left {
text-align:left;
#content div.box table th.right {
text-align:right;
#content div.box table th.center {
text-align:center;
#content div.box table th.selected {
vertical-align:middle;
#content div.box table td {
border-bottom:1px solid #cdcdcd;
padding:5px;
#content div.box table tr.selected td {
background:#FFC;
#content div.box table td.selected {
width:3%;
#content div.box table td.action {
width:45%;
#content div.box table td.date {
width:33%;
#content div.box div.action {
margin:10px 0 0;
#content div.box div.action select {
#content div.box div.action .ui-selectmenu {
#content div.box div.pagination {
#content div.box div.pagination ul.pager {
#content div.box div.pagination ul.pager li {
background:#ebebeb url("../images/pager.png") repeat-x;
border-top:1px solid #dedede;
border-left:1px solid #cfcfcf;
border-right:1px solid #c4c4c4;
border-bottom:1px solid #c4c4c4;
color:#4A4A4A;
margin:0 0 0 4px;
#content div.box div.pagination ul.pager li.separator {
padding:6px;
#content div.box div.pagination ul.pager li.current {
background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
border-top:1px solid #ccc;
border-left:1px solid #bebebe;
border-right:1px solid #b1b1b1;
border-bottom:1px solid #afafaf;
color:#515151;
#content div.box div.pagination ul.pager li a {
#content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active {
margin:-1px;
#content div.box div.pagination-wh {
#content div.box div.pagination-right {
#content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot {
#content div.box div.pagination-wh span.pager_curpage {
#content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active {
#content div.box div.traffic div.legend {
#content div.box div.traffic div.legend h6 {
#content div.box div.traffic div.legend li {
padding:0 8px 0 4px;
#content div.box div.traffic div.legend li.visits {
border-left:12px solid #edc240;
#content div.box div.traffic div.legend li.pageviews {
border-left:12px solid #afd8f8;
#content div.box div.traffic table {
width:auto;
#content div.box div.traffic table td {
padding:2px 3px 3px;
#content div.box div.traffic table td.legendLabel {
padding:0 3px 2px;
#summary{
#summary .desc{
white-space: pre;
width: 100%;
#summary .repo_name{
font-size: 1.6em;
font-weight: bold;
vertical-align: baseline;
clear:right
#footer {
padding:0 10px 4px;
margin:-10px 0 0;
#footer div#footer-inner {
border-top:2px solid #FFFFFF;
#footer div#footer-inner p {
padding:15px 25px 15px 0;
#footer div#footer-inner .footer-link {
padding-left:10px;
#footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a {
#login div.title {
width:420px;
margin:0 auto;
#login div.inner {
width:380px;
background:#FFF url("../images/login.png") no-repeat top left;
border-top:none;
border-bottom:none;
padding:20px;
#login div.form div.fields div.field div.label {
width:173px;
margin:2px 10px 0 0;
padding:5px 0 0 5px;
#login div.form div.fields div.field div.input input {
width:176px;
border-top:1px solid #b3b3b3;
border-left:1px solid #b3b3b3;
border-right:1px solid #eaeaea;
border-bottom:1px solid #eaeaea;
padding:7px 7px 6px;
#login div.form div.fields div.buttons {
border-top:1px solid #DDD;
padding:10px 0 0;
#login div.form div.links {
padding:0 0 2px;
#quick_login{
top: 31px;
background-color: rgb(0, 51, 103);
z-index: 999;
height: 150px;
position: absolute;
margin-left: -16px;
width: 281px;
border-radius: 0 0 8px 8px;
#quick_login .password_forgoten{
padding-right:10px;
padding-top:10px;
#quick_login div.form div.fields{
padding-top: 2px;
#quick_login div.form div.fields div.field{
padding: 5px;
#quick_login div.form div.fields div.field div.label label{
padding-bottom: 3px;
#quick_login div.form div.fields div.field div.input input {
width:236px;
padding:5px 7px 4px;
#quick_login div.form div.fields div.buttons {
padding:10px 14px 0;
#quick_login div.form div.fields div.buttons input.ui-button{
background:#e5e3e3 url("../images/button.png") repeat-x;
border-left:1px solid #c6c6c6;
border-right:1px solid #DDD;
border-bottom:1px solid #c6c6c6;
padding:4px 10px;
#quick_login div.form div.links {
#register div.title {
#register div.inner {
#register div.form div.fields div.field div.label {
width:135px;
#register div.form div.fields div.field div.input input {
width:300px;
#register div.form div.fields div.buttons {
padding:10px 0 0 150px;
#register div.form div.fields div.buttons div.highlight input.ui-button {
background:url("../images/button_highlight.png") repeat-x scroll 0 0 #4E85BB;
border-color:#5C91A4 #2B7089 #1A6480 #2A6F89;
border-style:solid;
border-width:1px;
#register div.form div.activation_msg {
padding-bottom:4px;
#journal .journal_day{
padding:10px 0px;
border-bottom:2px solid #DDD;
margin-left:10px;
margin-right:10px;
#journal .journal_container{
margin:0px 5px 0px 10px;
#journal .journal_action_container{
padding-left:38px;
#journal .journal_user{
color: #747474;
font-size: 14px;
height: 30px;
#journal .journal_icon{
clear: both;
float: left;
padding-right: 4px;
padding-top: 3px;
#journal .journal_action{
min-height:2px;
float:left
#journal .journal_action_params{
clear: left;
padding-left: 22px;
#journal .journal_repo{
margin-left: 6px;
#journal .date{
color: #777777;
font-size: 11px;
#journal .journal_repo .journal_repo_name{
font-size: 1.1em;
#journal .compare_view{
padding: 5px 0px 5px 0px;
width: 95px;
.journal_highlight{
padding: 0 2px;
vertical-align: bottom;
.trending_language_tbl,.trending_language_tbl td {
border:0 !important;
margin:0 !important;
padding:0 !important;
.trending_language {
background-color:#003367;
min-width:20px;
height:12px;
margin-bottom:4px;
margin-left:5px;
white-space:pre;
padding:3px;
h3.files_location {
font-size:1.8em;
border-bottom:none !important;
margin:10px 0 !important;
#files_data dl dt {
width:115px;
#files_data dl dd {
padding:5px !important;
#changeset_content {
border:1px solid #CCC;
#changeset_compare_view_content{
#changeset_content .container {
min-height:120px;
font-size:1.2em;
#changeset_compare_view_content .compare_view_commits{
width: auto !important;
#changeset_compare_view_content .compare_view_commits td{
padding:0px 0px 0px 12px !important;
#changeset_content .container .right {
width:25%;
#changeset_content .container .left .message {
font-style:italic;
color:#556CB5;
white-space:pre-wrap;
.cs_files .cur_cs{
margin:10px 2px;
.cs_files .node{
.cs_files .changes{
float: right;
.cs_files .changes .added{
background-color: #BBFFBB;
text-align: center;
font-size: 90%;
.cs_files .changes .deleted{
background-color: #FF8888;
.cs_files .cs_added {
background:url("../images/icons/page_white_add.png") no-repeat scroll 3px;
height:16px;
padding-left:20px;
margin-top:7px;
.cs_files .cs_changed {
background:url("../images/icons/page_white_edit.png") no-repeat scroll 3px;
.cs_files .cs_removed {
background:url("../images/icons/page_white_delete.png") no-repeat scroll 3px;
#graph {
#graph_nodes {
width:160px;
margin-left:-50px;
margin-top:5px;
#graph_content {
width:800px;
#graph_content .container_header {
padding:10px;
#graph_content #rev_range_container{
#graph_content .container {
border-bottom:1px solid #CCC;
border-left:1px solid #CCC;
border-right:1px solid #CCC;
min-height:80px;
#graph_content .container .right {
width:28%;
padding-bottom:5px;
#graph_content .container .left .date {
#graph_content .container .left .date span{
vertical-align: text-top;
#graph_content .container .left .message {
padding-top:3px;
.right div {
.right .changes .added,.changed,.removed {
border:1px solid #DDD;
min-width:15px;
cursor: help;
.right .changes .large {
min-width:45px;
background: #54A9F7;
.right .changes .added {
background:#BFB;
.right .changes .changed {
background:#FD8;
.right .changes .removed {
## -*- coding: utf-8 -*-
<%inherit file="root.html"/>
<!-- HEADER -->
<div id="header">
<!-- user -->
<ul id="logged-user">
<li class="first">
<div id="quick_login" style="display:none">
${h.form(h.url('login_home',came_from=h.url.current()))}
<div class="form">
<div class="fields">
<div class="field">
<div class="label">
<label for="username">${_('Username')}:</label>
</div>
<div class="input">
${h.text('username',class_='focus',size=40)}
<label for="password">${_('Password')}:</label>
${h.password('password',class_='focus',size=40)}
<div class="buttons">
${h.submit('sign_in','Sign In',class_="ui-button")}
<div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>${h.submit('sign_in','Sign In',class_="ui-button")}
${h.end_form()}
<script type="text/javascript">
YUE.on('quick_login_link','click',function(e){
if(YUD.hasClass('quick_login_link','enabled')){
YUD.setStyle('quick_login','display','none');
YUD.removeClass('quick_login_link','enabled');
else{
YUD.setStyle('quick_login','display','');
YUD.addClass('quick_login_link','enabled');
YUD.get('username').focus();
//make sure we don't redirect
YUE.preventDefault(e);
});
</script>
<div class="gravatar">
<img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
<div class="account">
%if c.rhodecode_user.username == 'default':
<a href="${h.url('public_journal')}">${_('Public journal')}</a>
%else:
${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
%endif
</li>
<li>
<a href="${h.url('home')}">${_('Home')}</a>
%if c.rhodecode_user.username != 'default':
<a href="${h.url('journal')}">${_('Journal')}</a>
##(${c.unread_journal}
<li class="last highlight">${h.link_to(u'Login',h.url('login_home'),id='quick_login_link')}</li>
<li class="last highlight">${h.link_to(u'Log Out',h.url('logout_home'))}</li>
</ul>
<!-- end user -->
<div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
<div id="logo">
<h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
<!-- MENU -->
${self.page_nav()}
<!-- END MENU -->
${self.body()}
<!-- END HEADER -->
<!-- CONTENT -->
<div id="content">
<div class="flash_msg">
<% messages = h.flash.pop_messages() %>
% if messages:
<ul id="flash-messages">
% for message in messages:
<li class="${message.category}_msg">${message}</li>
% endfor
% endif
<div id="main">
${next.main()}
<!-- END CONTENT -->
<!-- FOOTER -->
<div id="footer">
<div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
<div>
<p class="footer-link">
<a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
</p>
<p class="footer-link-right">
<a href="${h.url('rhodecode_official')}">RhodeCode</a>
${c.rhodecode_version} © 2010-${h.datetime.today().year} by Marcin Kuzminski
function tooltip_activate(){
${h.tooltip.activate()}
tooltip_activate();
<!-- END FOOTER -->
### MAKO DEFS ###
<%def name="page_nav()">
${self.menu()}
</%def>
<%def name="breadcrumbs()">
<div class="breadcrumbs">
${self.breadcrumbs_links()}
<%def name="menu(current=None)">
<%
def is_current(selected):
if selected == current:
return h.literal('class="current"')
%>
%if current not in ['home','admin']:
##REGULAR MENU
<ul id="quick">
<!-- repo switcher -->
<a id="repo_switcher" title="${_('Switch repository')}" href="#">
<span class="icon">
<img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
</span>
<span>↓</span>
</a>
<ul id="repo_switcher_list" class="repo_switcher">
<a href="#">${_('loading...')}</a>
YUE.on('repo_switcher','mouseover',function(){
function qfilter(){
var S = YAHOO.util.Selector;
var q_filter = YUD.get('q_filter_rs');
var F = YAHOO.namespace('q_filter_rs');
YUE.on(q_filter,'click',function(){
q_filter.value = '';
F.filterTimeout = null;
F.updateFilter = function() {
// Reset timeout
var obsolete = [];
var nodes = S.query('ul#repo_switcher_list li a.repo_name');
var req = YUD.get('q_filter_rs').value;
for (n in nodes){
YUD.setStyle(nodes[n].parentNode,'display','')
if (req){
console.log(n);
if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
obsolete.push(nodes[n]);
if(obsolete){
for (n in obsolete){
YUD.setStyle(obsolete[n].parentNode,'display','none');
YUE.on(q_filter,'keyup',function(e){
clearTimeout(F.filterTimeout);
setTimeout(F.updateFilter,600);
var loaded = YUD.hasClass('repo_switcher','loaded');
if(!loaded){
YUD.addClass('repo_switcher','loaded');
YAHOO.util.Connect.asyncRequest('GET',"${h.url('repo_switcher')}",{
success:function(o){
YUD.get('repo_switcher_list').innerHTML = o.responseText;
qfilter();
},
failure:function(o){
YUD.removeClass('repo_switcher','loaded');
},null);
return false;
<li ${is_current('summary')}>
<a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
<img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
<span>${_('Summary')}</span>
##<li ${is_current('shortlog')}>
## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
## <span class="icon">
## <img src="${h.url("/images/icons/application_view_list.png")}" alt="${_('Shortlog')}" />
## </span>
## <span>${_('Shortlog')}</span>
## </a>
##</li>
<li ${is_current('changelog')}>
<a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
<img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
<span>${_('Changelog')}</span>
<li ${is_current('switch_to')}>
<a title="${_('Switch to')}" href="#">
<img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
<span>${_('Switch to')}</span>
<ul>
${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
%if c.rhodecode_repo.branches.values():
%for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
<li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
%endfor
<li>${h.link_to(_('There are no branches yet'),'#')}</li>
${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
%if c.rhodecode_repo.tags.values():
%for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
<li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
<li>${h.link_to(_('There are no tags yet'),'#')}</li>
<li ${is_current('files')}>
<a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
<img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
<span>${_('Files')}</span>
<li ${is_current('options')}>
<a title="${_('Options')}" href="#">
<img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
<span>${_('Options')}</span>
%if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
%if h.HasPermissionAll('hg.admin')('access settings on repository'):
<li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
<li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
<li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
<li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
% if h.HasPermissionAll('hg.admin')('access admin main page'):
${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
<%def name="admin_menu()">
<li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
<li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
<li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
<li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
<li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
<li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
<li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
<li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
${admin_menu()}
<a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
<span class="icon_short">
<img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
<span id="current_followers_count" class="short">${c.repository_followers}</span>
<a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
<img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
<span class="short">${c.repository_forks}</span>
##ROOT MENU
<a title="${_('Home')}" href="${h.url('home')}">
<img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
<span>${_('Home')}</span>
% if c.rhodecode_user.username != 'default':
<a title="${_('Journal')}" href="${h.url('journal')}">
<img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
<span>${_('Journal')}</span>
<a title="${_('Search')}" href="${h.url('search')}">
<img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
<span>${_('Search')}</span>
%if h.HasPermissionAll('hg.admin')('access admin main page'):
<li ${is_current('admin')}>
<a title="${_('Admin')}" href="${h.url('admin_home')}">
<img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
<span>${_('Admin')}</span>
\ No newline at end of file
<%inherit file="base/root.html"/>
<%def name="title()">
${_('Reset You password')} - ${c.rhodecode_name}
<div id="register">
<div class="title top-left-rounded-corner top-right-rounded-corner">
<h5>${_('Reset You password to')} ${c.rhodecode_name}</h5>
<div class="inner">
${h.form(url('password_reset'))}
<!-- fields -->
<label for="email">${_('Email address')}:</label>
${h.text('email')}
<div class="nohighlight">
${h.submit('send','Reset my password',class_="ui-button")}
<div class="activation_msg">${_('Your new password will be send to matching email address')}</div>
<div class="activation_msg">${_('Password reset link will be send to matching email address')}</div>
YUE.onDOMReady(function(){
YUD.get('email').focus();
})
new file 100644
Status change: