Mercurial > kallithea
changeset 2476:19d94d752952 beta
merge branch codereview into beta
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Mon, 18 Jun 2012 00:35:13 +0200 |
parents | 930db0673614 (current diff) 6474e138737e (diff) |
children | 8eab81115660 |
files | |
diffstat | 71 files changed, 4984 insertions(+), 1131 deletions(-) [+] |
line wrap: on
line diff
--- a/requires.txt Mon Jun 18 00:33:19 2012 +0200 +++ b/requires.txt Mon Jun 18 00:35:13 2012 +0200 @@ -2,7 +2,7 @@ Beaker==1.6.3 WebHelpers==1.3 formencode==1.2.4 -SQLAlchemy==0.7.6 +SQLAlchemy==0.7.8 Mako==0.7.0 pygments>=1.4 whoosh>=2.4.0,<2.5
--- a/rhodecode/__init__.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/__init__.py Mon Jun 18 00:35:13 2012 +0200 @@ -38,7 +38,7 @@ __version__ = ('.'.join((str(each) for each in VERSION[:3])) + '.'.join(VERSION[3:])) -__dbversion__ = 5 # defines current db version for migrations +__dbversion__ = 6 # defines current db version for migrations __platform__ = platform.system() __license__ = 'GPLv3' __py_version__ = sys.version_info @@ -54,7 +54,7 @@ "Beaker==1.6.3", "WebHelpers==1.3", "formencode==1.2.4", - "SQLAlchemy==0.7.6", + "SQLAlchemy==0.7.8", "Mako==0.7.0", "pygments>=1.4", "whoosh>=2.4.0,<2.5",
--- a/rhodecode/config/routing.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/config/routing.py Mon Jun 18 00:35:13 2012 +0200 @@ -212,6 +212,10 @@ #EXTRAS USER ROUTES m.connect("user_perm", "/users_perm/{id}", action="update_perm", conditions=dict(method=["PUT"])) + m.connect("user_emails", "/users_emails/{id}", + action="add_email", conditions=dict(method=["PUT"])) + m.connect("user_emails_delete", "/users_emails/{id}", + action="delete_email", conditions=dict(method=["DELETE"])) #ADMIN USERS GROUPS REST ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, @@ -424,6 +428,41 @@ controller='changeset', action='raw_changeset', revision='tip', conditions=dict(function=check_repo)) + rmap.connect('compare_url', + '/{repo_name:.*}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}', + controller='compare', action='index', + conditions=dict(function=check_repo), + requirements=dict(org_ref_type='(branch|book|tag)', + other_ref_type='(branch|book|tag)')) + + rmap.connect('pullrequest_home', + '/{repo_name:.*}/pull-request/new', controller='pullrequests', + action='index', conditions=dict(function=check_repo, + method=["GET"])) + + rmap.connect('pullrequest', + '/{repo_name:.*}/pull-request/new', controller='pullrequests', + action='create', conditions=dict(function=check_repo, + method=["POST"])) + + rmap.connect('pullrequest_show', + '/{repo_name:.*}/pull-request/{pull_request_id}', + controller='pullrequests', + action='show', conditions=dict(function=check_repo, + method=["GET"])) + + rmap.connect('pullrequest_show_all', + '/{repo_name:.*}/pull-request', + controller='pullrequests', + action='show_all', conditions=dict(function=check_repo, + method=["GET"])) + + rmap.connect('pullrequest_comment', + '/{repo_name:.*}/pull-request-comment/{pull_request_id}', + controller='pullrequests', + action='comment', conditions=dict(function=check_repo, + method=["POST"])) + rmap.connect('summary_home', '/{repo_name:.*}/summary', controller='summary', conditions=dict(function=check_repo))
--- a/rhodecode/controllers/admin/notifications.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/controllers/admin/notifications.py Mon Jun 18 00:35:13 2012 +0200 @@ -60,19 +60,23 @@ """GET /_admin/notifications: All items in the collection""" # url('notifications') c.user = self.rhodecode_user - notif = NotificationModel().get_for_user(self.rhodecode_user.user_id) + notif = NotificationModel().get_for_user(self.rhodecode_user.user_id, + filter_=request.GET) p = int(request.params.get('page', 1)) c.notifications = Page(notif, page=p, items_per_page=10) + c.pull_request_type = Notification.TYPE_PULL_REQUEST return render('admin/notifications/notifications.html') def mark_all_read(self): if request.environ.get('HTTP_X_PARTIAL_XHR'): nm = NotificationModel() # mark all read - nm.mark_all_read_for_user(self.rhodecode_user.user_id) + nm.mark_all_read_for_user(self.rhodecode_user.user_id, + filter_=request.GET) Session.commit() c.user = self.rhodecode_user - notif = nm.get_for_user(self.rhodecode_user.user_id) + notif = nm.get_for_user(self.rhodecode_user.user_id, + filter_=request.GET) c.notifications = Page(notif, page=1, items_per_page=10) return render('admin/notifications/notifications_data.html')
--- a/rhodecode/controllers/admin/repos.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/controllers/admin/repos.py Mon Jun 18 00:35:13 2012 +0200 @@ -28,7 +28,7 @@ import formencode from formencode import htmlfill -from paste.httpexceptions import HTTPInternalServerError +from webob.exc import HTTPInternalServerError from pylons import request, session, tmpl_context as c, url from pylons.controllers.util import redirect from pylons.i18n.translation import _
--- a/rhodecode/controllers/admin/users.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/controllers/admin/users.py Mon Jun 18 00:35:13 2012 +0200 @@ -39,7 +39,7 @@ AuthUser from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import User, Permission +from rhodecode.model.db import User, Permission, UserEmailMap from rhodecode.model.forms import UserForm from rhodecode.model.user import UserModel from rhodecode.model.meta import Session @@ -179,7 +179,8 @@ c.user.permissions = {} c.granted_permissions = UserModel().fill_perms(c.user)\ .permissions['global'] - + c.user_email_map = UserEmailMap.query()\ + .filter(UserEmailMap.user == c.user).all() defaults = c.user.get_dict() perm = Permission.get_by_key('hg.create.repository') defaults.update({'create_repo_perm': UserModel().has_perm(id, perm)}) @@ -217,3 +218,30 @@ category='success') Session.commit() return redirect(url('edit_user', id=id)) + + def add_email(self, id): + """POST /user_emails:Add an existing item""" + # url('user_emails', id=ID, method='put') + + #TODO: validation and form !!! + email = request.POST.get('new_email') + user_model = UserModel() + + try: + user_model.add_extra_email(id, email) + Session.commit() + h.flash(_("Added email %s to user" % email), category='success') + except Exception: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during email saving'), + category='error') + return redirect(url('edit_user', id=id)) + + def delete_email(self, id): + """DELETE /user_emails_delete/id: Delete an existing item""" + # url('user_emails_delete', id=ID, method='delete') + user_model = UserModel() + user_model.delete_extra_email(id, request.POST.get('del_email')) + Session.commit() + h.flash(_("Removed email from user"), category='success') + return redirect(url('edit_user', id=id))
--- a/rhodecode/controllers/branches.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/controllers/branches.py Mon Jun 18 00:35:13 2012 +0200 @@ -24,14 +24,15 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import logging +import binascii from pylons import tmpl_context as c -import binascii from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.compat import OrderedDict from rhodecode.lib.utils2 import safe_unicode + log = logging.getLogger(__name__)
--- a/rhodecode/controllers/changelog.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/controllers/changelog.py Mon Jun 18 00:35:13 2012 +0200 @@ -82,7 +82,7 @@ collection = list(c.pagination) page_revisions = [x.raw_id for x in collection] c.comments = c.rhodecode_db_repo.comments(page_revisions) - + c.statuses = c.rhodecode_db_repo.statuses(page_revisions) except (RepositoryError, ChangesetDoesNotExistError, Exception), e: log.error(traceback.format_exc()) h.flash(str(e), category='warning')
--- a/rhodecode/controllers/changeset.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/controllers/changeset.py Mon Jun 18 00:35:13 2012 +0200 @@ -43,8 +43,9 @@ from rhodecode.lib.utils import EmptyChangeset, action_logger from rhodecode.lib.compat import OrderedDict from rhodecode.lib import diffs -from rhodecode.model.db import ChangesetComment +from rhodecode.model.db import ChangesetComment, ChangesetStatus from rhodecode.model.comment import ChangesetCommentsModel +from rhodecode.model.changeset_status import ChangesetStatusModel from rhodecode.model.meta import Session from rhodecode.lib.diffs import wrapped_diff from rhodecode.model.repo import RepoModel @@ -205,18 +206,24 @@ cumulative_diff = 0 c.cut_off = False # defines if cut off limit is reached - + c.changeset_statuses = ChangesetStatus.STATUSES c.comments = [] + c.statuses = [] c.inline_comments = [] c.inline_cnt = 0 # Iterate over ranges (default changeset view is always one changeset) for changeset in c.cs_ranges: + + c.statuses.extend([ChangesetStatusModel()\ + .get_status(c.rhodecode_db_repo.repo_id, + changeset.raw_id)]) + c.comments.extend(ChangesetCommentsModel()\ .get_comments(c.rhodecode_db_repo.repo_id, - changeset.raw_id)) + revision=changeset.raw_id)) inlines = ChangesetCommentsModel()\ .get_inline_comments(c.rhodecode_db_repo.repo_id, - changeset.raw_id) + revision=changeset.raw_id) c.inline_comments.extend(inlines) c.changes[changeset.raw_id] = [] try: @@ -288,7 +295,7 @@ ) # count inline comments - for path, lines in c.inline_comments: + for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) @@ -365,14 +372,29 @@ @jsonify def comment(self, repo_name, revision): + status = request.POST.get('changeset_status') + change_status = request.POST.get('change_changeset_status') + comm = ChangesetCommentsModel().create( text=request.POST.get('text'), repo_id=c.rhodecode_db_repo.repo_id, user_id=c.rhodecode_user.user_id, revision=revision, f_path=request.POST.get('f_path'), - line_no=request.POST.get('line') + line_no=request.POST.get('line'), + status_change=(ChangesetStatus.get_status_lbl(status) + if status and change_status else None) ) + + # get status if set ! + if status and change_status: + ChangesetStatusModel().set_status( + c.rhodecode_db_repo.repo_id, + status, + c.rhodecode_user.user_id, + comm, + revision=revision, + ) action_logger(self.rhodecode_user, 'user_commented_revision:%s' % revision, c.rhodecode_db_repo, self.ip_addr, self.sa)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/controllers/compare.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.compare + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + compare controller for pylons showoing differences between two + repos, branches, bookmarks or tips + + :created_on: May 6, 2012 + :author: marcink + :copyright: (C) 2010-2012 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 traceback + +from webob.exc import HTTPNotFound +from pylons import request, response, session, tmpl_context as c, url +from pylons.controllers.util import abort, redirect + +from rhodecode.lib import helpers as h +from rhodecode.lib.base import BaseRepoController, render +from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator +from rhodecode.lib import diffs + +from rhodecode.model.db import Repository +from rhodecode.model.pull_request import PullRequestModel + +log = logging.getLogger(__name__) + + +class CompareController(BaseRepoController): + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') + def __before__(self): + super(CompareController, self).__before__() + + def index(self, org_ref_type, org_ref, other_ref_type, other_ref): + + org_repo = c.rhodecode_db_repo.repo_name + org_ref = (org_ref_type, org_ref) + other_ref = (other_ref_type, other_ref) + other_repo = request.GET.get('repo', org_repo) + + c.swap_url = h.url('compare_url', repo_name=other_repo, + org_ref_type=other_ref[0], org_ref=other_ref[1], + other_ref_type=org_ref[0], other_ref=org_ref[1], + repo=org_repo) + + c.org_repo = org_repo = Repository.get_by_repo_name(org_repo) + c.other_repo = other_repo = Repository.get_by_repo_name(other_repo) + + if c.org_repo is None or c.other_repo is None: + log.error('Could not found repo %s or %s' % (org_repo, other_repo)) + raise HTTPNotFound + + if c.org_repo.scm_instance.alias != 'hg': + log.error('Review not available for GIT REPOS') + raise HTTPNotFound + + c.cs_ranges, discovery_data = PullRequestModel().get_compare_data( + org_repo, org_ref, other_repo, other_ref + ) + + c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in + c.cs_ranges]) + # defines that we need hidden inputs with changesets + c.as_form = request.GET.get('as_form', False) + if request.environ.get('HTTP_X_PARTIAL_XHR'): + return render('compare/compare_cs.html') + + c.org_ref = org_ref[1] + c.other_ref = other_ref[1] + # diff needs to have swapped org with other to generate proper diff + _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref, + discovery_data) + diff_processor = diffs.DiffProcessor(_diff, format='gitdiff') + _parsed = diff_processor.prepare() + + c.files = [] + c.changes = {} + + for f in _parsed: + fid = h.FID('', f['filename']) + c.files.append([fid, f['operation'], f['filename'], f['stats']]) + diff = diff_processor.as_html(enable_comments=False, diff_lines=[f]) + c.changes[fid] = [f['operation'], f['filename'], diff] + + return render('compare/compare_diff.html')
--- a/rhodecode/controllers/home.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/controllers/home.py Mon Jun 18 00:35:13 2012 +0200 @@ -26,7 +26,7 @@ import logging from pylons import tmpl_context as c, request -from paste.httpexceptions import HTTPBadRequest +from webob.exc import HTTPBadRequest from rhodecode.lib.auth import LoginRequired from rhodecode.lib.base import BaseController, render
--- a/rhodecode/controllers/journal.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/controllers/journal.py Mon Jun 18 00:35:13 2012 +0200 @@ -30,7 +30,7 @@ from webhelpers.paginate import Page from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed -from paste.httpexceptions import HTTPBadRequest +from webob.exc import HTTPBadRequest from pylons import request, tmpl_context as c, response, url from pylons.i18n.translation import _
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/controllers/pullrequests.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,277 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.pullrequests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + pull requests controller for rhodecode for initializing pull requests + + :created_on: May 7, 2012 + :author: marcink + :copyright: (C) 2010-2012 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 traceback + +from webob.exc import HTTPNotFound + +from pylons import request, response, session, tmpl_context as c, url +from pylons.controllers.util import abort, redirect +from pylons.i18n.translation import _ +from pylons.decorators import jsonify + +from rhodecode.lib.base import BaseRepoController, render +from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator +from rhodecode.lib import helpers as h +from rhodecode.lib import diffs +from rhodecode.lib.utils import action_logger +from rhodecode.model.db import User, PullRequest, ChangesetStatus +from rhodecode.model.pull_request import PullRequestModel +from rhodecode.model.meta import Session +from rhodecode.model.repo import RepoModel +from rhodecode.model.comment import ChangesetCommentsModel +from rhodecode.model.changeset_status import ChangesetStatusModel + +log = logging.getLogger(__name__) + + +class PullrequestsController(BaseRepoController): + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') + def __before__(self): + super(PullrequestsController, self).__before__() + + def _get_repo_refs(self, repo): + hist_l = [] + + branches_group = ([('branch:%s:%s' % (k, v), k) for + k, v in repo.branches.iteritems()], _("Branches")) + bookmarks_group = ([('book:%s:%s' % (k, v), k) for + k, v in repo.bookmarks.iteritems()], _("Bookmarks")) + tags_group = ([('tag:%s:%s' % (k, v), k) for + k, v in repo.tags.iteritems()], _("Tags")) + + hist_l.append(bookmarks_group) + hist_l.append(branches_group) + hist_l.append(tags_group) + + return hist_l + + def show_all(self, repo_name): + c.pull_requests = PullRequestModel().get_all(repo_name) + c.repo_name = repo_name + return render('/pullrequests/pullrequest_show_all.html') + + def index(self): + org_repo = c.rhodecode_db_repo + + if org_repo.scm_instance.alias != 'hg': + log.error('Review not available for GIT REPOS') + raise HTTPNotFound + + c.org_refs = self._get_repo_refs(c.rhodecode_repo) + c.org_repos = [] + c.other_repos = [] + c.org_repos.append((org_repo.repo_name, '%s/%s' % ( + org_repo.user.username, c.repo_name)) + ) + + c.other_refs = c.org_refs + c.other_repos.extend(c.org_repos) + c.default_pull_request = org_repo.repo_name + #gather forks and add to this list + for fork in org_repo.forks: + c.other_repos.append((fork.repo_name, '%s/%s' % ( + fork.user.username, fork.repo_name)) + ) + #add parents of this fork also + if org_repo.parent: + c.default_pull_request = org_repo.parent.repo_name + c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % ( + org_repo.parent.user.username, + org_repo.parent.repo_name)) + ) + + c.review_members = [] + c.available_members = [] + for u in User.query().filter(User.username != 'default').all(): + uname = u.username + if org_repo.user == u: + uname = _('%s (owner)' % u.username) + # auto add owner to pull-request recipients + c.review_members.append([u.user_id, uname]) + c.available_members.append([u.user_id, uname]) + return render('/pullrequests/pullrequest.html') + + def create(self, repo_name): + req_p = request.POST + org_repo = req_p['org_repo'] + org_ref = req_p['org_ref'] + other_repo = req_p['other_repo'] + other_ref = req_p['other_ref'] + revisions = req_p.getall('revisions') + reviewers = req_p.getall('review_members') + #TODO: wrap this into a FORM !!! + + title = req_p['pullrequest_title'] + description = req_p['pullrequest_desc'] + + try: + model = PullRequestModel() + model.create(self.rhodecode_user.user_id, org_repo, + org_ref, other_repo, other_ref, revisions, + reviewers, title, description) + Session.commit() + h.flash(_('Pull request send'), category='success') + except Exception: + raise + h.flash(_('Error occured during sending pull request'), + category='error') + log.error(traceback.format_exc()) + + return redirect(url('changelog_home', repo_name=repo_name)) + + def _load_compare_data(self, pull_request): + """ + Load context data needed for generating compare diff + + :param pull_request: + :type pull_request: + """ + + org_repo = pull_request.org_repo + org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':') + other_repo = pull_request.other_repo + other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':') + + org_ref = (org_ref_type, org_ref) + other_ref = (other_ref_type, other_ref) + + c.org_repo = org_repo + c.other_repo = other_repo + + c.cs_ranges, discovery_data = PullRequestModel().get_compare_data( + org_repo, org_ref, other_repo, other_ref + ) + + c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in + c.cs_ranges]) + # defines that we need hidden inputs with changesets + c.as_form = request.GET.get('as_form', False) + + c.org_ref = org_ref[1] + c.other_ref = other_ref[1] + # diff needs to have swapped org with other to generate proper diff + _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref, + discovery_data) + diff_processor = diffs.DiffProcessor(_diff, format='gitdiff') + _parsed = diff_processor.prepare() + + c.files = [] + c.changes = {} + + for f in _parsed: + fid = h.FID('', f['filename']) + c.files.append([fid, f['operation'], f['filename'], f['stats']]) + diff = diff_processor.as_html(enable_comments=False, diff_lines=[f]) + c.changes[fid] = [f['operation'], f['filename'], diff] + + def show(self, repo_name, pull_request_id): + repo_model = RepoModel() + c.users_array = repo_model.get_users_js() + c.users_groups_array = repo_model.get_users_groups_js() + c.pull_request = PullRequest.get(pull_request_id) + + # valid ID + if not c.pull_request: + raise HTTPNotFound + + # pull_requests repo_name we opened it against + # ie. other_repo must match + if repo_name != c.pull_request.other_repo.repo_name: + raise HTTPNotFound + + # load compare data into template context + self._load_compare_data(c.pull_request) + + # inline comments + c.inline_cnt = 0 + c.inline_comments = ChangesetCommentsModel()\ + .get_inline_comments(c.rhodecode_db_repo.repo_id, + pull_request=pull_request_id) + # count inline comments + for __, lines in c.inline_comments: + for comments in lines.values(): + c.inline_cnt += len(comments) + # comments + c.comments = ChangesetCommentsModel()\ + .get_comments(c.rhodecode_db_repo.repo_id, + pull_request=pull_request_id) + + # changeset(pull-request) status + c.current_changeset_status = ChangesetStatusModel()\ + .get_status(c.pull_request.org_repo, + pull_request=c.pull_request) + c.changeset_statuses = ChangesetStatus.STATUSES + return render('/pullrequests/pullrequest_show.html') + + @jsonify + def comment(self, repo_name, pull_request_id): + + status = request.POST.get('changeset_status') + change_status = request.POST.get('change_changeset_status') + + comm = ChangesetCommentsModel().create( + text=request.POST.get('text'), + repo_id=c.rhodecode_db_repo.repo_id, + user_id=c.rhodecode_user.user_id, + pull_request=pull_request_id, + f_path=request.POST.get('f_path'), + line_no=request.POST.get('line'), + status_change=(ChangesetStatus.get_status_lbl(status) + if status and change_status else None) + ) + + # get status if set ! + if status and change_status: + ChangesetStatusModel().set_status( + c.rhodecode_db_repo.repo_id, + status, + c.rhodecode_user.user_id, + comm, + pull_request=pull_request_id + ) + action_logger(self.rhodecode_user, + 'user_commented_pull_request:%s' % pull_request_id, + c.rhodecode_db_repo, self.ip_addr, self.sa) + + Session.commit() + + if not request.environ.get('HTTP_X_PARTIAL_XHR'): + return redirect(h.url('pullrequest_show', repo_name=repo_name, + pull_request_id=pull_request_id)) + + data = { + 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), + } + if comm: + c.co = comm + data.update(comm.get_dict()) + data.update({'rendered_text': + render('changeset/changeset_comment_block.html')}) + + return data
--- a/rhodecode/lib/base.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/lib/base.py Mon Jun 18 00:35:13 2012 +0200 @@ -204,7 +204,7 @@ super(BaseRepoController, self).__before__() if c.repo_name: - c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) + dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) c.rhodecode_repo = c.rhodecode_db_repo.scm_instance if c.rhodecode_repo is None: @@ -213,5 +213,7 @@ redirect(url('home')) - c.repository_followers = self.scm_model.get_followers(c.repo_name) - c.repository_forks = self.scm_model.get_forks(c.repo_name) + # some globals counter for menu + c.repository_followers = self.scm_model.get_followers(dbr) + c.repository_forks = self.scm_model.get_forks(dbr) + c.repository_pull_requests = self.scm_model.get_pull_requests(dbr) \ No newline at end of file
--- a/rhodecode/lib/db_manage.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/lib/db_manage.py Mon Jun 18 00:35:13 2012 +0200 @@ -178,6 +178,8 @@ def step_5(self): pass + def step_6(self): + pass upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1) # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
--- a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py Mon Jun 18 00:35:13 2012 +0200 @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """ - rhodecode.model.db - ~~~~~~~~~~~~~~~~~~ + rhodecode.model.db_1_2_0 + ~~~~~~~~~~~~~~~~~~~~~~~~ - Database Models for RhodeCode + Database Models for RhodeCode <=1.2.X :created_on: Apr 08, 2010 :author: marcink
--- a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py Mon Jun 18 00:35:13 2012 +0200 @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """ - rhodecode.model.db - ~~~~~~~~~~~~~~~~~~ + rhodecode.model.db_1_3_0 + ~~~~~~~~~~~~~~~~~~~~~~~~ - Database Models for RhodeCode + Database Models for RhodeCode <=1.3.X :created_on: Apr 08, 2010 :author: marcink @@ -23,6 +23,1270 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -#TODO: when branch 1.3 is finished replacem with db.py content +import os +import logging +import datetime +import traceback +from collections import defaultdict + +from sqlalchemy import * +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship, joinedload, class_mapper, validates +from beaker.cache import cache_region, region_invalidate + +from rhodecode.lib.vcs import get_backend +from rhodecode.lib.vcs.utils.helpers import get_scm +from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.utils.lazy import LazyProperty + +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ + safe_unicode +from rhodecode.lib.compat import json +from rhodecode.lib.caching_query import FromCache + +from rhodecode.model.meta import Base, Session +import hashlib + + +log = logging.getLogger(__name__) + +#============================================================================== +# BASE CLASSES +#============================================================================== + +_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() + + +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__() + else: + 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) + + # also use __json__() if present to get additional fields + for k, val in getattr(self, '__json__', lambda: {})().iteritems(): + d[k] = val + return d + + def get_appstruct(self): + """return list with keys and values tupples corresponding + to this model data """ + + l = [] + for k in self._get_keys(): + l.append((k, getattr(self, k),)) + return l + + def populate_obj(self, populate_dict): + """populate model with data from given populate_dict""" + + for k in self._get_keys(): + if k in populate_dict: + setattr(self, k, populate_dict[k]) + + @classmethod + def query(cls): + return Session.query(cls) + + @classmethod + def get(cls, id_): + if id_: + return cls.query().get(id_) + + @classmethod + def getAll(cls): + return cls.query().all() + + @classmethod + def delete(cls, id_): + obj = cls.query().get(id_) + Session.delete(obj) + + def __repr__(self): + if hasattr(self, '__unicode__'): + # python repr needs to return str + return safe_str(self.__unicode__()) + return '<DB:%s>' % (self.__class__.__name__) + +class RhodeCodeSetting(Base, BaseModel): + __tablename__ = 'rhodecode_settings' + __table_args__ = ( + UniqueConstraint('app_settings_name'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + 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 + + @validates('_app_settings_value') + def validate_settings_value(self, key, val): + assert type(val) == unicode + return val + + @hybrid_property + def app_settings_value(self): + v = self._app_settings_value + if self.app_settings_name == 'ldap_active': + v = str2bool(v) + return v + + @app_settings_value.setter + def app_settings_value(self, val): + """ + Setter that will always make sure we use unicode in app_settings_value + + :param val: + """ + self._app_settings_value = safe_unicode(val) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.app_settings_name, self.app_settings_value + ) + + @classmethod + def get_by_name(cls, ldap_key): + return cls.query()\ + .filter(cls.app_settings_name == ldap_key).scalar() + + @classmethod + def get_app_settings(cls, cache=False): + + ret = cls.query() + + 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 + + @classmethod + def get_ldap_settings(cls, cache=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('ldap_')).all() + fd = {} + for row in ret: + fd.update({row.app_settings_name:row.app_settings_value}) + + return fd + + +class RhodeCodeUi(Base, BaseModel): + __tablename__ = 'rhodecode_ui' + __table_args__ = ( + UniqueConstraint('ui_key'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + + HOOK_UPDATE = 'changegroup.update' + HOOK_REPO_SIZE = 'changegroup.repo_size' + HOOK_PUSH = 'pretxnchangegroup.push_logger' + HOOK_PULL = 'preoutgoing.pull_logger' + + 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) + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.ui_key == key) + + @classmethod + def get_builtin_hooks(cls): + q = cls.query() + q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, + cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PULL])) + return q.all() + + @classmethod + def get_custom_hooks(cls): + q = cls.query() + q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, + cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PULL])) + q = q.filter(cls.ui_section == 'hooks') + return q.all() + + @classmethod + def create_or_update_hook(cls, key, val): + new_ui = cls.get_by_key(key).scalar() or cls() + new_ui.ui_section = 'hooks' + new_ui.ui_active = True + new_ui.ui_key = key + new_ui.ui_value = val + + Session.add(new_ui) + + +class User(Base, BaseModel): + __tablename__ = 'users' + __table_args__ = ( + UniqueConstraint('username'), UniqueConstraint('email'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + 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('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') + repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') + + group_member = relationship('UsersGroupMember', cascade='all') + + notifications = relationship('UserNotification', cascade='all') + # notifications assigned to this user + user_created_notifications = relationship('Notification', cascade='all') + # comments created by this user + user_comments = relationship('ChangesetComment', cascade='all') + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + @property + def full_name(self): + return '%s %s' % (self.name, self.lastname) + + @property + def full_name_or_username(self): + return ('%s %s' % (self.name, self.lastname) + if (self.name and self.lastname) else self.username) + + @property + def full_contact(self): + return '%s %s <%s>' % (self.name, self.lastname, self.email) + + @property + def short_contact(self): + return '%s %s' % (self.name, self.lastname) + + @property + def is_admin(self): + return self.admin + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.user_id, self.username) + + @classmethod + def get_by_username(cls, username, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.username.ilike(username)) + else: + q = cls.query().filter(cls.username == username) + + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(username) + ) + ) + return q.scalar() + + @classmethod + def get_by_api_key(cls, api_key, cache=False): + q = cls.query().filter(cls.api_key == api_key) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_api_key_%s" % api_key)) + return q.scalar() + + @classmethod + def get_by_email(cls, email, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.email.ilike(email)) + else: + q = cls.query().filter(cls.email == email) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_api_key_%s" % email)) + return q.scalar() + + def update_lastlogin(self): + """Update user lastlogin""" + self.last_login = datetime.datetime.now() + Session.add(self) + log.debug('updated user %s lastlogin' % self.username) + + def __json__(self): + return dict( + user_id=self.user_id, + first_name=self.name, + last_name=self.lastname, + email=self.email, + full_name=self.full_name, + full_name_or_username=self.full_name_or_username, + short_contact=self.short_contact, + full_contact=self.full_contact + ) + + +class UserLog(Base, BaseModel): + __tablename__ = 'user_logs' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + 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=True) + 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) + + @property + def action_as_day(self): + return datetime.date(*self.action_date.timetuple()[:3]) + + user = relationship('User') + repository = relationship('Repository', cascade='') + + +class UsersGroup(Base, BaseModel): + __tablename__ = 'users_groups' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + + 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") + users_group_to_perm = relationship('UsersGroupToPerm', cascade='all') + users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all') + + def __unicode__(self): + return u'<userGroup(%s)>' % (self.users_group_name) + + @classmethod + def get_by_group_name(cls, group_name, cache=False, + case_insensitive=False): + if case_insensitive: + q = cls.query().filter(cls.users_group_name.ilike(group_name)) + else: + q = cls.query().filter(cls.users_group_name == group_name) + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(group_name) + ) + ) + return q.scalar() + + @classmethod + def get(cls, users_group_id, cache=False): + users_group = cls.query() + if cache: + users_group = users_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % users_group_id)) + return users_group.get(users_group_id) + + +class UsersGroupMember(Base, BaseModel): + __tablename__ = 'users_groups_members' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + + 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_id = Column("user_id", Integer(), ForeignKey('users.user_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, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + + 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) + + user = relationship('User') + fork = relationship('Repository', remote_side=repo_id) + group = relationship('RepoGroup') + repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.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') + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id, + self.repo_name) + + @classmethod + def url_sep(cls): + return '/' + + @classmethod + def get_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.scalar() + + @classmethod + def get_repo_forks(cls, repo_id): + return cls.query().filter(Repository.fork_id == repo_id) + + @classmethod + def base_path(cls): + """ + Returns base path when all repos are stored + + :param cls: + """ + q = Session.query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == cls.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def just_name(self): + return self.repo_name.split(Repository.url_sep())[-1] + + @property + 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: + break + groups.insert(0, gr) + + return groups + + @property + 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 == + Repository.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + 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(Repository.url_sep()) + return os.path.join(*p) + + def get_new_name(self, repo_name): + """ + returns new full repository name based on assigned group and new new + + :param group_name: + """ + path_prefix = self.group.full_path_splitted if self.group else [] + return Repository.url_sep().join(path_prefix + [repo_name]) + + @property + 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 = RhodeCodeUi.query()\ + .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 + + @classmethod + def is_valid(cls, repo_name): + """ + returns True if given repo name is a valid filesystem repository + + :param cls: + :param repo_name: + """ + from rhodecode.lib.utils import is_valid_repo + + return is_valid_repo(repo_name, cls.base_path()) + + #========================================================================== + # SCM PROPERTIES + #========================================================================== + + def get_changeset(self, rev): + return get_changeset_safe(self.scm_instance, rev) + + @property + def tip(self): + return self.get_changeset('tip') + + @property + def author(self): + return self.tip.author -from rhodecode.model.db import * + @property + def last_change(self): + return self.scm_instance.last_change + + def comments(self, revisions=None): + """ + Returns comments for this repository grouped by revisions + + :param revisions: filter query by revisions only + """ + cmts = ChangesetComment.query()\ + .filter(ChangesetComment.repo == self) + if revisions: + cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) + grouped = defaultdict(list) + for cmt in cmts.all(): + grouped[cmt.revision].append(cmt) + return grouped + + #========================================================================== + # SCM CACHE INSTANCE + #========================================================================== + + @property + def invalidate(self): + return CacheInvalidation.invalidate(self.repo_name) + + def set_invalidate(self): + """ + set a cache for invalidation for this instance + """ + CacheInvalidation.set_invalidate(self.repo_name) + + @LazyProperty + def scm_instance(self): + return self.__get_instance() + + @property + def scm_instance_cached(self): + @cache_region('long_term') + def _c(repo_name): + return self.__get_instance() + rn = self.repo_name + log.debug('Getting cached instance of repo') + inv = self.invalidate + if inv is not None: + region_invalidate(_c, None, rn) + # update our cache + CacheInvalidation.set_valid(inv.cache_key) + return _c(rn) + + def __get_instance(self): + repo_full_path = self.repo_full_path + try: + alias = get_scm(repo_full_path)[0] + log.debug('Creating instance of %s repository' % alias) + backend = get_backend(alias) + except VCSError: + log.error(traceback.format_exc()) + 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(): + return + else: + repo = backend(repo_full_path, create=False) + + return repo + + +class RepoGroup(Base, BaseModel): + __tablename__ = 'groups' + __table_args__ = ( + UniqueConstraint('group_name', 'group_parent_id'), + CheckConstraint('group_id != group_parent_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + __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) + + repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') + users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all') + + parent_group = relationship('RepoGroup', remote_side=group_id) + + def __init__(self, group_name='', parent_group=None): + self.group_name = group_name + self.parent_group = parent_group + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id, + self.group_name) + + @classmethod + def groups_choices(cls): + from webhelpers.html import literal as _literal + repo_groups = [('', '')] + sep = ' » ' + _name = lambda k: _literal(sep.join(k)) + + repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) + for x in cls.query().all()]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) + return repo_groups + + @classmethod + def url_sep(cls): + return '/' + + @classmethod + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = cls.query()\ + .filter(cls.group_name.ilike(group_name)) + else: + gr = cls.query()\ + .filter(cls.group_name == group_name) + if cache: + gr = gr.options(FromCache( + "sql_cache_short", + "get_group_%s" % _hash_key(group_name) + ) + ) + return gr.scalar() + + @property + def parents(self): + parents_recursion_limit = 5 + groups = [] + if self.parent_group is None: + return groups + cur_gr = self.parent_group + groups.insert(0, cur_gr) + cnt = 0 + while 1: + cnt += 1 + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + if cnt == parents_recursion_limit: + # this will prevent accidental infinit loops + log.error('group nested more than %s' % + parents_recursion_limit) + break + + groups.insert(0, gr) + return groups + + @property + def children(self): + return RepoGroup.query().filter(RepoGroup.parent_group == self) + + @property + def name(self): + return self.group_name.split(RepoGroup.url_sep())[-1] + + @property + def full_path(self): + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(RepoGroup.url_sep()) + + @property + def repositories(self): + return Repository.query()\ + .filter(Repository.group == self)\ + .order_by(Repository.repo_name) + + @property + def repositories_recursive_count(self): + cnt = self.repositories.count() + + def children_count(group): + cnt = 0 + for child in group.children: + cnt += child.repositories.count() + cnt += children_count(child) + return cnt + + return cnt + children_count(self) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = (self.parent_group.full_path_splitted if + self.parent_group else []) + return RepoGroup.url_sep().join(path_prefix + [group_name]) + + +class Permission(Base, BaseModel): + __tablename__ = 'permissions' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + 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) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, self.permission_id, self.permission_name + ) + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.permission_name == key).scalar() + + @classmethod + def get_default_perms(cls, default_user_id): + q = Session.query(UserRepoToPerm, Repository, cls)\ + .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ + .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_group_perms(cls, default_user_id): + q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\ + .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoGroupToPerm.user_id == default_user_id) + + return q.all() + + +class UserRepoToPerm(Base, BaseModel): + __tablename__ = 'repo_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'repository_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + repo_to_perm_id = Column("repo_to_perm_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) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + repository = relationship('Repository') + permission = relationship('Permission') + + @classmethod + def create(cls, user, repository, permission): + n = cls() + n.user = user + n.repository = repository + n.permission = permission + Session.add(n) + return n + + def __unicode__(self): + return u'<user:%s => %s >' % (self.user, self.repository) + + +class UserToPerm(Base, BaseModel): + __tablename__ = 'user_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + user_to_perm_id = Column("user_to_perm_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) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + permission = relationship('Permission', lazy='joined') + + +class UsersGroupRepoToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_to_perm' + __table_args__ = ( + UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + users_group_to_perm_id = Column("users_group_to_perm_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) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UsersGroup') + permission = relationship('Permission') + repository = relationship('Repository') + + @classmethod + def create(cls, users_group, repository, permission): + n = cls() + n.users_group = users_group + n.repository = repository + n.permission = permission + Session.add(n) + return n + + def __unicode__(self): + return u'<userGroup:%s => %s >' % (self.users_group, self.repository) + + +class UsersGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'permission_id',), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + users_group_to_perm_id = Column("users_group_to_perm_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) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UsersGroup') + permission = relationship('Permission') + + +class UserRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'user_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + + group_to_perm_id = Column("group_to_perm_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) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + group = relationship('RepoGroup') + permission = relationship('Permission') + + +class UsersGroupRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'group_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + + users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_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) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UsersGroup') + permission = relationship('Permission') + group = relationship('RepoGroup') + + +class Statistics(Base, BaseModel): + __tablename__ = 'statistics' + __table_args__ = ( + UniqueConstraint('repository_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data + + repository = relationship('Repository', single_parent=True) + + +class UserFollowing(Base, BaseModel): + __tablename__ = 'user_followings' + __table_args__ = ( + UniqueConstraint('user_id', 'follows_repository_id'), + UniqueConstraint('user_id', 'follows_user_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + + user_following_id = Column("user_following_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) + follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) + follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + + user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') + + follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') + follows_repository = relationship('Repository', order_by='Repository.repo_name') + + @classmethod + def get_repo_followers(cls, repo_id): + return cls.query().filter(cls.follows_repo_id == repo_id) + + +class CacheInvalidation(Base, BaseModel): + __tablename__ = 'cache_invalidation' + __table_args__ = ( + UniqueConstraint('cache_key'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + def __init__(self, cache_key, cache_args=''): + self.cache_key = cache_key + self.cache_args = cache_args + self.cache_active = False + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, + self.cache_id, self.cache_key) + @classmethod + def clear_cache(cls): + cls.query().delete() + + @classmethod + def _get_key(cls, key): + """ + Wrapper for generating a key, together with a prefix + + :param key: + """ + import rhodecode + prefix = '' + iid = rhodecode.CONFIG.get('instance_id') + if iid: + prefix = iid + return "%s%s" % (prefix, key), prefix, key.rstrip('_README') + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.cache_key == key).scalar() + + @classmethod + def _get_or_create_key(cls, key, prefix, org_key): + inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar() + if not inv_obj: + try: + inv_obj = CacheInvalidation(key, org_key) + Session.add(inv_obj) + Session.commit() + except Exception: + log.error(traceback.format_exc()) + Session.rollback() + return inv_obj + + @classmethod + def invalidate(cls, key): + """ + Returns Invalidation object if this given key should be invalidated + None otherwise. `cache_active = False` means that this cache + state is not valid and needs to be invalidated + + :param key: + """ + + key, _prefix, _org_key = cls._get_key(key) + inv = cls._get_or_create_key(key, _prefix, _org_key) + + if inv and inv.cache_active is False: + return inv + + @classmethod + def set_invalidate(cls, key): + """ + Mark this Cache key for invalidation + + :param key: + """ + + key, _prefix, _org_key = cls._get_key(key) + inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all() + log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs), + _org_key)) + try: + for inv_obj in inv_objs: + if inv_obj: + inv_obj.cache_active = False + + Session.add(inv_obj) + Session.commit() + except Exception: + log.error(traceback.format_exc()) + Session.rollback() + + @classmethod + def set_valid(cls, key): + """ + Mark this cache key as active and currently cached + + :param key: + """ + inv_obj = cls.get_by_key(key) + inv_obj.cache_active = True + Session.add(inv_obj) + Session.commit() + + +class ChangesetComment(Base, BaseModel): + __tablename__ = 'changeset_comments' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + revision = Column('revision', String(40), nullable=False) + line_no = Column('line_no', Unicode(10), nullable=True) + f_path = Column('f_path', Unicode(1000), nullable=True) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) + text = Column('text', Unicode(25000), nullable=False) + modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + + @classmethod + def get_users(cls, revision): + """ + Returns user associated with this changesetComment. ie those + who actually commented + + :param cls: + :param revision: + """ + return Session.query(User)\ + .filter(cls.revision == revision)\ + .join(ChangesetComment.author).all() + + +class Notification(Base, BaseModel): + __tablename__ = 'notifications' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + + TYPE_CHANGESET_COMMENT = u'cs_comment' + TYPE_MESSAGE = u'message' + TYPE_MENTION = u'mention' + TYPE_REGISTRATION = u'registration' + + notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) + subject = Column('subject', Unicode(512), nullable=True) + body = Column('body', Unicode(50000), nullable=True) + created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + type_ = Column('type', Unicode(256)) + + created_by_user = relationship('User') + notifications_to_users = relationship('UserNotification', lazy='joined', + cascade="all, delete, delete-orphan") + + @property + def recipients(self): + return [x.user for x in UserNotification.query()\ + .filter(UserNotification.notification == self).all()] + + @classmethod + def create(cls, created_by, subject, body, recipients, type_=None): + if type_ is None: + type_ = Notification.TYPE_MESSAGE + + notification = cls() + notification.created_by_user = created_by + notification.subject = subject + notification.body = body + notification.type_ = type_ + notification.created_on = datetime.datetime.now() + + for u in recipients: + assoc = UserNotification() + assoc.notification = notification + u.notifications.append(assoc) + Session.add(notification) + return notification + + @property + def description(self): + from rhodecode.model.notification import NotificationModel + return NotificationModel().make_description(self) + + +class UserNotification(Base, BaseModel): + __tablename__ = 'user_to_notification' + __table_args__ = ( + UniqueConstraint('user_id', 'notification_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) + notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) + read = Column('read', Boolean, default=False) + sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) + + user = relationship('User', lazy="joined") + notification = relationship('Notification', lazy="joined", + order_by=lambda: Notification.created_on.desc(),) + + def mark_as_read(self): + self.read = True + Session.add(self) + + +class DbMigrateVersion(Base, BaseModel): + __tablename__ = 'db_migrate_version' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) + repository_id = Column('repository_id', String(250), primary_key=True) + repository_path = Column('repository_path', Text) + version = Column('version', Integer)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/schema/db_1_4_0.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.model.db_1_4_0 + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Database Models for RhodeCode <=1.4.X + + :created_on: Apr 08, 2010 + :author: marcink + :copyright: (C) 2010-2012 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/>. + +#TODO: replace that will db.py content after 1.5 Release + +from rhodecode.model.db import * \ No newline at end of file
--- a/rhodecode/lib/diffs.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/lib/diffs.py Mon Jun 18 00:35:13 2012 +0200 @@ -26,16 +26,23 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import re +import io import difflib import markupsafe + from itertools import tee, imap +from mercurial import patch +from mercurial.mdiff import diffopts +from mercurial.bundlerepo import bundlerepository +from mercurial import localrepo + from pylons.i18n.translation import _ from rhodecode.lib.vcs.exceptions import VCSError from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode from rhodecode.lib.helpers import escape -from rhodecode.lib.utils import EmptyChangeset +from rhodecode.lib.utils import EmptyChangeset, make_ui def wrap_to_table(str_): @@ -379,6 +386,7 @@ }) line = lineiter.next() + except StopIteration: pass @@ -445,7 +453,7 @@ new_lineno_class='lineno old', old_lineno_class='lineno new', code_class='code', enable_comments=False, diff_lines=None): """ - Return udiff as html table with customized css classes + Return given diff as html table with customized css classes """ def _link_to_if(condition, label, url): """ @@ -542,3 +550,78 @@ Returns tuple of added, and removed lines for this instance """ return self.adds, self.removes + + +class InMemoryBundleRepo(bundlerepository): + def __init__(self, ui, path, bundlestream): + self._tempparent = None + localrepo.localrepository.__init__(self, ui, path) + self.ui.setconfig('phases', 'publish', False) + + self.bundle = bundlestream + + # dict with the mapping 'filename' -> position in the bundle + self.bundlefilespos = {} + + +def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None): + """ + General differ between branches, bookmarks or separate but releated + repositories + + :param org_repo: + :type org_repo: + :param org_ref: + :type org_ref: + :param other_repo: + :type other_repo: + :param other_ref: + :type other_ref: + """ + + bundlerepo = None + ignore_whitespace = False + context = 3 + org_repo = org_repo.scm_instance._repo + other_repo = other_repo.scm_instance._repo + opts = diffopts(git=True, ignorews=ignore_whitespace, context=context) + org_ref = org_ref[1] + other_ref = other_ref[1] + + if org_repo != other_repo: + + common, incoming, rheads = discovery_data + + # create a bundle (uncompressed if other repo is not local) + if other_repo.capable('getbundle') and incoming: + # disable repo hooks here since it's just bundle ! + # patch and reset hooks section of UI config to not run any + # hooks on fetching archives with subrepos + for k, _ in other_repo.ui.configitems('hooks'): + other_repo.ui.setconfig('hooks', k, None) + + unbundle = other_repo.getbundle('incoming', common=common, + heads=rheads) + + buf = io.BytesIO() + while True: + chunk = unbundle._stream.read(1024 * 4) + if not chunk: + break + buf.write(chunk) + + buf.seek(0) + # replace chunked _stream with data that can do tell() and seek() + unbundle._stream = buf + + ui = make_ui('db') + bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root, + bundlestream=unbundle) + + return ''.join(patch.diff(bundlerepo or org_repo, + node1=org_repo[org_ref].node(), + node2=other_repo[other_ref].node(), + opts=opts)) + else: + return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref, + opts=opts))
--- a/rhodecode/lib/helpers.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/lib/helpers.py Mon Jun 18 00:35:13 2012 +0200 @@ -45,11 +45,26 @@ from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError from rhodecode.lib.vcs.backends.base import BaseChangeset from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT +from rhodecode.model.changeset_status import ChangesetStatusModel from rhodecode.model.db import URL_SEP log = logging.getLogger(__name__) +html_escape_table = { + "&": "&", + '"': """, + "'": "'", + ">": ">", + "<": "<", +} + + +def html_escape(text): + """Produce entities within text.""" + return "".join(html_escape_table.get(c,c) for c in text) + + def shorter(text, size=20): postfix = '...' if len(text) > size: @@ -340,7 +355,7 @@ #============================================================================== from rhodecode.lib.vcs.utils import author_name, author_email from rhodecode.lib.utils2 import credentials_filter, age as _age -from rhodecode.model.db import User +from rhodecode.model.db import User, ChangesetStatus age = lambda x: _age(x) capitalize = lambda x: x.capitalize() @@ -982,3 +997,11 @@ """ return literal('<div class="rst-block">%s</div>' % MarkupRenderer.rst_with_mentions(source)) + + +def changeset_status(repo, revision): + return ChangesetStatusModel().get_status(repo, revision) + + +def changeset_status_lbl(changeset_status): + return dict(ChangesetStatus.STATUSES).get(changeset_status)
--- a/rhodecode/lib/profiler.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/lib/profiler.py Mon Jun 18 00:35:13 2012 +0200 @@ -1,5 +1,7 @@ from __future__ import with_statement +import gc +import objgraph import cProfile import pstats import cgi @@ -26,7 +28,7 @@ profiler.snapshot_stats() stats = pstats.Stats(profiler) - stats.sort_stats('cumulative') + stats.sort_stats('calls') #cummulative # Redirect output out = StringIO() @@ -44,6 +46,11 @@ 'border-top: 4px dashed red; padding: 1em;">') resp += cgi.escape(out.getvalue(), True) + ct = objgraph.show_most_common_types() + print ct + + resp += ct if ct else '---' + output = StringIO() pprint.pprint(environ, output, depth=3)
--- a/rhodecode/lib/vcs/nodes.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/lib/vcs/nodes.py Mon Jun 18 00:35:13 2012 +0200 @@ -431,8 +431,10 @@ name, kind or state (or methods/attributes checking those two) would raise RemovedFileNodeError. """ - ALLOWED_ATTRIBUTES = ['name', 'path', 'state', 'is_root', 'is_file', - 'is_dir', 'kind', 'added', 'changed', 'not_changed', 'removed'] + ALLOWED_ATTRIBUTES = [ + 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind', + 'added', 'changed', 'not_changed', 'removed' + ] def __init__(self, path): """
--- a/rhodecode/lib/vcs/utils/hgcompat.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/lib/vcs/utils/hgcompat.py Mon Jun 18 00:35:13 2012 +0200 @@ -12,3 +12,4 @@ from mercurial.mdiff import diffopts from mercurial.node import hex from mercurial.encoding import tolocal +from mercurial import discovery \ No newline at end of file
--- a/rhodecode/model/__init__.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/__init__.py Mon Jun 18 00:35:13 2012 +0200 @@ -42,7 +42,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import logging - +from rhodecode.model.db import User, Repository, Permission from rhodecode.model import meta log = logging.getLogger(__name__) @@ -96,3 +96,33 @@ ) else: return callback(instance) + + def _get_user(self, user): + """ + Helper method to get user by ID, or username fallback + + :param user: + :type user: UserID, username, or User instance + """ + return self._get_instance(User, user, + callback=User.get_by_username) + + def _get_repo(self, repository): + """ + Helper method to get repository by ID, or repository name + + :param repository: + :type repository: RepoID, repository name or Repository Instance + """ + return self._get_instance(Repository, repository, + callback=Repository.get_by_repo_name) + + def _get_perm(self, permission): + """ + Helper method to get permission by ID, or permission name + + :param permission: + :type permission: PermissionID, permission_name or Permission instance + """ + return self._get_instance(Permission, permission, + callback=Permission.get_by_key)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/model/changeset_status.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.model.changeset_status + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + :created_on: Apr 30, 2012 + :author: marcink + :copyright: (C) 2011-2012 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 + +from rhodecode.model import BaseModel +from rhodecode.model.db import ChangesetStatus, PullRequest + +log = logging.getLogger(__name__) + + +class ChangesetStatusModel(BaseModel): + + def __get_changeset_status(self, changeset_status): + return self._get_instance(ChangesetStatus, changeset_status) + + def __get_pull_request(self, pull_request): + return self._get_instance(PullRequest, pull_request) + + def get_status(self, repo, revision=None, pull_request=None): + """ + Returns latest status of changeset for given revision or for given + pull request. Statuses are versioned inside a table itself and + version == 0 is always the current one + + :param repo: + :type repo: + :param revision: 40char hash or None + :type revision: str + :param pull_request: pull_request reference + :type: + """ + repo = self._get_repo(repo) + + q = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == repo)\ + .filter(ChangesetStatus.version == 0) + + if revision: + q = q.filter(ChangesetStatus.revision == revision) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetStatus.pull_request == pull_request) + else: + raise Exception('Please specify revision or pull_request') + + # need to use first here since there can be multiple statuses + # returned from pull_request + status = q.first() + status = status.status if status else status + st = status or ChangesetStatus.DEFAULT + return str(st) + + def set_status(self, repo, status, user, comment, revision=None, + pull_request=None): + """ + Creates new status for changeset or updates the old ones bumping their + version, leaving the current status at + + :param repo: + :type repo: + :param revision: + :type revision: + :param status: + :type status: + :param user: + :type user: + :param comment: + :type comment: + """ + repo = self._get_repo(repo) + + q = ChangesetStatus.query() + + if revision: + q = q.filter(ChangesetStatus.repo == repo) + q = q.filter(ChangesetStatus.revision == revision) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetStatus.repo == pull_request.org_repo) + q = q.filter(ChangesetStatus.pull_request == pull_request) + cur_statuses = q.all() + + if cur_statuses: + for st in cur_statuses: + st.version += 1 + self.sa.add(st) + + def _create_status(user, repo, status, comment, revision, pull_request): + new_status = ChangesetStatus() + new_status.author = self._get_user(user) + new_status.repo = self._get_repo(repo) + new_status.status = status + new_status.comment = comment + new_status.revision = revision + new_status.pull_request = pull_request + return new_status + + if revision: + new_status = _create_status(user=user, repo=repo, status=status, + comment=comment, revision=revision, + pull_request=None) + self.sa.add(new_status) + return new_status + elif pull_request: + #pull request can have more than one revision associated to it + #we need to create new version for each one + new_statuses = [] + repo = pull_request.org_repo + for rev in pull_request.revisions: + new_status = _create_status(user=user, repo=repo, + status=status, comment=comment, + revision=rev, + pull_request=pull_request) + new_statuses.append(new_status) + self.sa.add(new_status) + return new_statuses
--- a/rhodecode/model/comment.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/comment.py Mon Jun 18 00:35:13 2012 +0200 @@ -32,7 +32,8 @@ from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode from rhodecode.lib import helpers as h from rhodecode.model import BaseModel -from rhodecode.model.db import ChangesetComment, User, Repository, Notification +from rhodecode.model.db import ChangesetComment, User, Repository, \ + Notification, PullRequest from rhodecode.model.notification import NotificationModel log = logging.getLogger(__name__) @@ -43,6 +44,9 @@ def __get_changeset_comment(self, changeset_comment): return self._get_instance(ChangesetComment, changeset_comment) + def __get_pull_request(self, pull_request): + return self._get_instance(PullRequest, pull_request) + def _extract_mentions(self, s): user_objects = [] for username in extract_mentioned_users(s): @@ -51,36 +55,54 @@ user_objects.append(user_obj) return user_objects - def create(self, text, repo_id, user_id, revision, f_path=None, - line_no=None): + def create(self, text, repo_id, user_id, revision=None, pull_request=None, + f_path=None, line_no=None, status_change=None): """ - Creates new comment for changeset + Creates new comment for changeset or pull request. + IF status_change is not none this comment is associated with a + status change of changeset or changesets associated with pull request :param text: :param repo_id: :param user_id: :param revision: + :param pull_request: :param f_path: :param line_no: + :param status_change: """ + if not text: + return - if text: - repo = Repository.get(repo_id) + repo = Repository.get(repo_id) + comment = ChangesetComment() + comment.repo = repo + comment.user_id = user_id + comment.text = text + comment.f_path = f_path + comment.line_no = line_no + + if revision: cs = repo.scm_instance.get_changeset(revision) desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256)) author_email = cs.author_email - comment = ChangesetComment() - comment.repo = repo - comment.user_id = user_id comment.revision = revision - comment.text = text - comment.f_path = f_path - comment.line_no = line_no + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + comment.pull_request = pull_request + desc = '' + else: + raise Exception('Please specify revision or pull_request_id') - self.sa.add(comment) - self.sa.flush() - # make notification - line = '' + self.sa.add(comment) + self.sa.flush() + + # make notification + line = '' + body = text + + #changeset + if revision: if line_no: line = _('on line %s') % line_no subj = safe_unicode( @@ -93,32 +115,41 @@ ) ) ) - - body = text - + notification_type = Notification.TYPE_CHANGESET_COMMENT # get the current participants of this changeset recipients = ChangesetComment.get_users(revision=revision) - # add changeset author if it's in rhodecode system recipients += [User.get_by_email(author_email)] + #pull request + elif pull_request: + #TODO: make this something usefull + subj = 'commented on pull request something...' + notification_type = Notification.TYPE_PULL_REQUEST_COMMENT + # get the current participants of this pull request + recipients = ChangesetComment.get_users(pull_request_id= + pull_request.pull_request_id) + # add pull request author + recipients += [pull_request.author] - # create notification objects, and emails + # create notification objects, and emails + NotificationModel().create( + created_by=user_id, subject=subj, body=body, + recipients=recipients, type_=notification_type, + email_kwargs={'status_change': status_change} + ) + + mention_recipients = set(self._extract_mentions(body))\ + .difference(recipients) + if mention_recipients: + subj = _('[Mention]') + ' ' + subj NotificationModel().create( - created_by=user_id, subject=subj, body=body, - recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT + created_by=user_id, subject=subj, body=body, + recipients=mention_recipients, + type_=notification_type, + email_kwargs={'status_change': status_change} ) - mention_recipients = set(self._extract_mentions(body))\ - .difference(recipients) - if mention_recipients: - subj = _('[Mention]') + ' ' + subj - NotificationModel().create( - created_by=user_id, subject=subj, body=body, - recipients=mention_recipients, - type_=Notification.TYPE_CHANGESET_COMMENT - ) - - return comment + return comment def delete(self, comment): """ @@ -131,21 +162,47 @@ return comment - def get_comments(self, repo_id, revision): - return ChangesetComment.query()\ + def get_comments(self, repo_id, revision=None, pull_request=None): + """ + Get's main comments based on revision or pull_request_id + + :param repo_id: + :type repo_id: + :param revision: + :type revision: + :param pull_request: + :type pull_request: + """ + + q = ChangesetComment.query()\ .filter(ChangesetComment.repo_id == repo_id)\ - .filter(ChangesetComment.revision == revision)\ .filter(ChangesetComment.line_no == None)\ - .filter(ChangesetComment.f_path == None).all() + .filter(ChangesetComment.f_path == None) + if revision: + q = q.filter(ChangesetComment.revision == revision) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetComment.pull_request == pull_request) + else: + raise Exception('Please specify revision or pull_request') + return q.all() - def get_inline_comments(self, repo_id, revision): - comments = self.sa.query(ChangesetComment)\ + def get_inline_comments(self, repo_id, revision=None, pull_request=None): + q = self.sa.query(ChangesetComment)\ .filter(ChangesetComment.repo_id == repo_id)\ - .filter(ChangesetComment.revision == revision)\ .filter(ChangesetComment.line_no != None)\ .filter(ChangesetComment.f_path != None)\ .order_by(ChangesetComment.comment_id.asc())\ - .all() + + if revision: + q = q.filter(ChangesetComment.revision == revision) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetComment.pull_request == pull_request) + else: + raise Exception('Please specify revision or pull_request_id') + + comments = q.all() paths = defaultdict(lambda: defaultdict(list))
--- a/rhodecode/model/db.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/db.py Mon Jun 18 00:35:13 2012 +0200 @@ -33,8 +33,11 @@ from sqlalchemy import * from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import relationship, joinedload, class_mapper, validates +from sqlalchemy.exc import DatabaseError from beaker.cache import cache_region, region_invalidate +from pylons.i18n.translation import lazy_ugettext as _ + from rhodecode.lib.vcs import get_backend from rhodecode.lib.vcs.utils.helpers import get_scm from rhodecode.lib.vcs.exceptions import VCSError @@ -44,6 +47,7 @@ safe_unicode from rhodecode.lib.compat import json from rhodecode.lib.caching_query import FromCache + from rhodecode.model.meta import Base, Session @@ -384,8 +388,23 @@ if cache: q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % email)) - return q.scalar() + "get_email_key_%s" % email)) + + ret = q.scalar() + if ret is None: + q = UserEmailMap.query() + # try fetching in alternate email map + if case_insensitive: + q = q.filter(UserEmailMap.email.ilike(email)) + else: + q = q.filter(UserEmailMap.email == email) + q = q.options(joinedload(UserEmailMap.user)) + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_map_key_%s" % email)) + ret = getattr(q.scalar(), 'user', None) + + return ret def update_lastlogin(self): """Update user lastlogin""" @@ -406,6 +425,39 @@ ) +class UserEmailMap(Base, BaseModel): + __tablename__ = 'user_email_map' + __table_args__ = ( + Index('uem_email_idx', 'email'), + UniqueConstraint('email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8'} + ) + __mapper_args__ = {} + + email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + + user = relationship('User', lazy='joined') + + @validates('_email') + def validate_email(self, key, email): + # check if this email is not main one + main_email = Session.query(User).filter(User.email == email).scalar() + if main_email is not None: + raise AttributeError('email %s is present is user table' % email) + return email + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + class UserLog(Base, BaseModel): __tablename__ = 'user_logs' __table_args__ = ( @@ -522,6 +574,7 @@ followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all') logs = relationship('UserLog') + comments = relationship('ChangesetComment') def __unicode__(self): return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, @@ -561,6 +614,20 @@ return q.one().ui_value @property + def forks(self): + """ + Return forks of this repo + """ + return Repository.get_repo_forks(self.repo_id) + + @property + def parent(self): + """ + Returns fork parent + """ + return self.fork + + @property def just_name(self): return self.repo_name.split(Repository.url_sep())[-1] @@ -686,6 +753,29 @@ grouped[cmt.revision].append(cmt) return grouped + def statuses(self, revisions=None): + """ + Returns statuses for this repository + + :param revisions: list of revisions to get statuses for + :type revisions: list + """ + + statuses = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == self)\ + .filter(ChangesetStatus.version == 0) + if revisions: + statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) + grouped = {} + for stat in statuses.all(): + pr_id = pr_repo = None + if stat.pull_request: + pr_id = stat.pull_request.pull_request_id + pr_repo = stat.pull_request.other_repo.repo_name + grouped[stat.revision] = [str(stat.status), stat.status_lbl, + pr_id, pr_repo] + return grouped + #========================================================================== # SCM CACHE INSTANCE #========================================================================== @@ -887,6 +977,7 @@ class Permission(Base, BaseModel): __tablename__ = 'permissions' __table_args__ = ( + Index('p_perm_name_idx', 'permission_name'), {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'}, ) @@ -1234,12 +1325,14 @@ class ChangesetComment(Base, BaseModel): __tablename__ = 'changeset_comments' __table_args__ = ( + Index('cc_revision_idx', 'revision'), {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'}, ) comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=False) + revision = Column('revision', String(40), nullable=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) line_no = Column('line_no', Unicode(10), nullable=True) f_path = Column('f_path', Unicode(1000), nullable=True) user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) @@ -1248,24 +1341,129 @@ author = relationship('User', lazy='joined') repo = relationship('Repository') + status_change = relationship('ChangesetStatus', uselist=False) + pull_request = relationship('PullRequest', lazy='joined') @classmethod - def get_users(cls, revision): + def get_users(cls, revision=None, pull_request_id=None): """ - Returns user associated with this changesetComment. ie those + Returns user associated with this ChangesetComment. ie those who actually commented :param cls: :param revision: """ - return Session.query(User)\ - .filter(cls.revision == revision)\ - .join(ChangesetComment.author).all() + q = Session.query(User)\ + .join(ChangesetComment.author) + if revision: + q = q.filter(cls.revision == revision) + elif pull_request_id: + q = q.filter(cls.pull_request_id == pull_request_id) + return q.all() + + +class ChangesetStatus(Base, BaseModel): + __tablename__ = 'changeset_statuses' + __table_args__ = ( + Index('cs_revision_idx', 'revision'), + Index('cs_version_idx', 'version'), + UniqueConstraint('repo_id', 'revision', 'version'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8'} + ) + + STATUSES = [ + ('not_reviewed', _("Not Reviewed")), # (no icon) and default + ('approved', _("Approved")), + ('rejected', _("Rejected")), + ('under_review', _("Under Review")), + ] + DEFAULT = STATUSES[0][0] + + changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + revision = Column('revision', String(40), nullable=False) + status = Column('status', String(128), nullable=False, default=DEFAULT) + changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) + modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + version = Column('version', Integer(), nullable=False, default=0) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + comment = relationship('ChangesetComment', lazy='joined') + pull_request = relationship('PullRequest', lazy='joined') + + @classmethod + def get_status_lbl(cls, value): + return dict(cls.STATUSES).get(value) + + @property + def status_lbl(self): + return ChangesetStatus.get_status_lbl(self.status) + + +class PullRequest(Base, BaseModel): + __tablename__ = 'pull_requests' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8'}, + ) + + pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) + title = Column('title', Unicode(256), nullable=True) + description = Column('description', Unicode(10240), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max + org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + org_ref = Column('org_ref', Unicode(256), nullable=False) + other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + other_ref = Column('other_ref', Unicode(256), nullable=False) + + @hybrid_property + def revisions(self): + return self._revisions.split(':') + + @revisions.setter + def revisions(self, val): + self._revisions = ':'.join(val) + + author = relationship('User', lazy='joined') + reviewers = relationship('PullRequestReviewers') + org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') + other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') + + def __json__(self): + return dict( + revisions=self.revisions + ) + + +class PullRequestReviewers(Base, BaseModel): + __tablename__ = 'pull_request_reviewers' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8'}, + ) + + def __init__(self, user=None, pull_request=None): + self.user = user + self.pull_request = pull_request + + pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) + + user = relationship('User') + pull_request = relationship('PullRequest') class Notification(Base, BaseModel): __tablename__ = 'notifications' __table_args__ = ( + Index('notification_type_idx', 'type'), {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'}, ) @@ -1274,6 +1472,8 @@ TYPE_MESSAGE = u'message' TYPE_MENTION = u'mention' TYPE_REGISTRATION = u'registration' + TYPE_PULL_REQUEST = u'pull_request' + TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) subject = Column('subject', Unicode(512), nullable=True)
--- a/rhodecode/model/forms.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/forms.py Mon Jun 18 00:35:13 2012 +0200 @@ -19,573 +19,76 @@ for SELECT use formencode.All(OneOf(list), Int()) """ -import os -import re import logging -import traceback import formencode from formencode import All -from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \ - Email, Bool, StringBoolean, Set from pylons.i18n.translation import _ -from webhelpers.pylonslib.secure_form import authentication_token -from rhodecode.config.routing import ADMIN_PREFIX -from rhodecode.lib.utils import repo_name_slug -from rhodecode.lib.auth import authenticate, get_crypt_password -from rhodecode.lib.exceptions import LdapImportError -from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository +from rhodecode.model import validators as v from rhodecode import BACKENDS log = logging.getLogger(__name__) -#this is needed to translate the messages using _() in validators -class State_obj(object): - _ = staticmethod(_) - - -#============================================================================== -# VALIDATORS -#============================================================================== -class ValidAuthToken(formencode.validators.FancyValidator): - messages = {'invalid_token': _('Token mismatch')} - - def validate_python(self, value, state): - - if value != authentication_token(): - raise formencode.Invalid( - self.message('invalid_token', - state, search_number=value), - value, - state - ) - - -def ValidUsername(edit, old_data): - class _ValidUsername(formencode.validators.FancyValidator): - - def validate_python(self, value, state): - if value in ['default', 'new_user']: - raise formencode.Invalid(_('Invalid username'), value, state) - #check if user is unique - old_un = None - if edit: - old_un = User.get(old_data.get('user_id')).username - - if old_un != value or not edit: - if User.get_by_username(value, case_insensitive=True): - raise formencode.Invalid(_('This username already ' - 'exists') , value, state) - - if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: - raise formencode.Invalid( - _('Username may only contain alphanumeric characters ' - 'underscores, periods or dashes and must begin with ' - 'alphanumeric character'), - value, - state - ) - - return _ValidUsername - - -def ValidUsersGroup(edit, old_data): - - class _ValidUsersGroup(formencode.validators.FancyValidator): - - def validate_python(self, value, state): - if value in ['default']: - raise formencode.Invalid(_('Invalid group name'), value, state) - #check if group is unique - old_ugname = None - if edit: - old_ugname = UsersGroup.get( - old_data.get('users_group_id')).users_group_name - - if old_ugname != value or not edit: - if UsersGroup.get_by_group_name(value, cache=False, - case_insensitive=True): - raise formencode.Invalid(_('This users group ' - 'already exists'), value, - state) - - if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: - raise formencode.Invalid( - _('RepoGroup name may only contain alphanumeric characters ' - 'underscores, periods or dashes and must begin with ' - 'alphanumeric character'), - value, - state - ) - - return _ValidUsersGroup - - -def ValidReposGroup(edit, old_data): - class _ValidReposGroup(formencode.validators.FancyValidator): - - def validate_python(self, value, state): - # TODO WRITE VALIDATIONS - group_name = value.get('group_name') - group_parent_id = value.get('group_parent_id') - - # slugify repo group just in case :) - slug = repo_name_slug(group_name) - - # check for parent of self - parent_of_self = lambda: ( - old_data['group_id'] == int(group_parent_id) - if group_parent_id else False - ) - if edit and parent_of_self(): - e_dict = { - 'group_parent_id': _('Cannot assign this group as parent') - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - old_gname = None - if edit: - old_gname = RepoGroup.get(old_data.get('group_id')).group_name - - if old_gname != group_name or not edit: - - # check group - gr = RepoGroup.query()\ - .filter(RepoGroup.group_name == slug)\ - .filter(RepoGroup.group_parent_id == group_parent_id)\ - .scalar() - - if gr: - e_dict = { - 'group_name': _('This group already exists') - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - # check for same repo - repo = Repository.query()\ - .filter(Repository.repo_name == slug)\ - .scalar() - - if repo: - e_dict = { - 'group_name': _('Repository with this name already exists') - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - return _ValidReposGroup - - -class ValidPassword(formencode.validators.FancyValidator): - - def to_python(self, value, state): - - if not value: - return - - if value.get('password'): - try: - value['password'] = get_crypt_password(value['password']) - except UnicodeEncodeError: - e_dict = {'password': _('Invalid characters in password')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - if value.get('password_confirmation'): - try: - value['password_confirmation'] = \ - get_crypt_password(value['password_confirmation']) - except UnicodeEncodeError: - e_dict = { - 'password_confirmation': _('Invalid characters in password') - } - raise formencode.Invalid('', value, state, error_dict=e_dict) - - if value.get('new_password'): - try: - value['new_password'] = \ - get_crypt_password(value['new_password']) - except UnicodeEncodeError: - e_dict = {'new_password': _('Invalid characters in password')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - return value - - -class ValidPasswordsMatch(formencode.validators.FancyValidator): - - def validate_python(self, value, state): - - pass_val = value.get('password') or value.get('new_password') - if pass_val != value['password_confirmation']: - e_dict = {'password_confirmation': - _('Passwords do not match')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - -class ValidAuth(formencode.validators.FancyValidator): - messages = { - 'invalid_password':_('invalid password'), - 'invalid_login':_('invalid user name'), - 'disabled_account':_('Your account is disabled') - } - - # error mapping - e_dict = {'username': messages['invalid_login'], - 'password': messages['invalid_password']} - e_dict_disable = {'username': messages['disabled_account']} - - def validate_python(self, value, state): - password = value['password'] - username = value['username'] - user = User.get_by_username(username) - - if authenticate(username, password): - return value - else: - if user and user.active is False: - log.warning('user %s is disabled' % username) - raise formencode.Invalid( - self.message('disabled_account', - state=State_obj), - value, state, - error_dict=self.e_dict_disable - ) - else: - log.warning('user %s failed to authenticate' % username) - raise formencode.Invalid( - self.message('invalid_password', - state=State_obj), value, state, - error_dict=self.e_dict - ) - - -class ValidRepoUser(formencode.validators.FancyValidator): - - def to_python(self, value, state): - try: - User.query().filter(User.active == True)\ - .filter(User.username == value).one() - except Exception: - raise formencode.Invalid(_('This username is not valid'), - value, state) - return value - - -def ValidRepoName(edit, old_data): - class _ValidRepoName(formencode.validators.FancyValidator): - def to_python(self, value, state): - - repo_name = value.get('repo_name') - - slug = repo_name_slug(repo_name) - if slug in [ADMIN_PREFIX, '']: - e_dict = {'repo_name': _('This repository name is disallowed')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - if value.get('repo_group'): - gr = RepoGroup.get(value.get('repo_group')) - group_path = gr.full_path - # value needs to be aware of group name in order to check - # db key This is an actual just the name to store in the - # database - repo_name_full = group_path + RepoGroup.url_sep() + repo_name - - else: - group_path = '' - repo_name_full = repo_name - - value['repo_name_full'] = repo_name_full - rename = old_data.get('repo_name') != repo_name_full - create = not edit - if rename or create: - - if group_path != '': - if Repository.get_by_repo_name(repo_name_full): - e_dict = { - 'repo_name': _('This repository already exists in ' - 'a group "%s"') % gr.group_name - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - elif RepoGroup.get_by_group_name(repo_name_full): - e_dict = { - 'repo_name': _('There is a group with this name ' - 'already "%s"') % repo_name_full - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - elif Repository.get_by_repo_name(repo_name_full): - e_dict = {'repo_name': _('This repository ' - 'already exists')} - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - return value - - return _ValidRepoName - - -def ValidForkName(*args, **kwargs): - return ValidRepoName(*args, **kwargs) - - -def SlugifyName(): - class _SlugifyName(formencode.validators.FancyValidator): - - def to_python(self, value, state): - return repo_name_slug(value) - - return _SlugifyName - - -def ValidCloneUri(): - from rhodecode.lib.utils import make_ui - - def url_handler(repo_type, url, proto, ui=None): - if repo_type == 'hg': - from mercurial.httprepo import httprepository, httpsrepository - if proto == 'https': - httpsrepository(make_ui('db'), url).capabilities - elif proto == 'http': - httprepository(make_ui('db'), url).capabilities - elif repo_type == 'git': - #TODO: write a git url validator - pass - - class _ValidCloneUri(formencode.validators.FancyValidator): - - def to_python(self, value, state): - - repo_type = value.get('repo_type') - url = value.get('clone_uri') - e_dict = {'clone_uri': _('invalid clone url')} - - if not url: - pass - elif url.startswith('https'): - try: - url_handler(repo_type, url, 'https', make_ui('db')) - except Exception: - log.error(traceback.format_exc()) - raise formencode.Invalid('', value, state, error_dict=e_dict) - elif url.startswith('http'): - try: - url_handler(repo_type, url, 'http', make_ui('db')) - except Exception: - log.error(traceback.format_exc()) - raise formencode.Invalid('', value, state, error_dict=e_dict) - else: - e_dict = {'clone_uri': _('Invalid clone url, provide a ' - 'valid clone http\s url')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - return value - - return _ValidCloneUri - - -def ValidForkType(old_data): - class _ValidForkType(formencode.validators.FancyValidator): - - def to_python(self, value, state): - if old_data['repo_type'] != value: - raise formencode.Invalid(_('Fork have to be the same ' - 'type as original'), value, state) - - return value - return _ValidForkType - - -def ValidPerms(type_='repo'): - if type_ == 'group': - EMPTY_PERM = 'group.none' - elif type_ == 'repo': - EMPTY_PERM = 'repository.none' - - class _ValidPerms(formencode.validators.FancyValidator): - messages = { - 'perm_new_member_name': - _('This username or users group name is not valid') - } - - def to_python(self, value, state): - perms_update = [] - perms_new = [] - # build a list of permission to update and new permission to create - for k, v in value.items(): - # means new added member to permissions - if k.startswith('perm_new_member'): - new_perm = value.get('perm_new_member', False) - new_member = value.get('perm_new_member_name', False) - new_type = value.get('perm_new_member_type') - - if new_member and new_perm: - if (new_member, new_perm, new_type) not in perms_new: - perms_new.append((new_member, new_perm, new_type)) - elif k.startswith('u_perm_') or k.startswith('g_perm_'): - member = k[7:] - t = {'u': 'user', - 'g': 'users_group' - }[k[0]] - if member == 'default': - if value.get('private'): - # set none for default when updating to private repo - v = EMPTY_PERM - perms_update.append((member, v, t)) - - value['perms_updates'] = perms_update - value['perms_new'] = perms_new - - # update permissions - for k, v, t in perms_new: - try: - if t is 'user': - self.user_db = User.query()\ - .filter(User.active == True)\ - .filter(User.username == k).one() - if t is 'users_group': - self.user_db = UsersGroup.query()\ - .filter(UsersGroup.users_group_active == True)\ - .filter(UsersGroup.users_group_name == k).one() - - except Exception: - msg = self.message('perm_new_member_name', - state=State_obj) - raise formencode.Invalid( - msg, value, state, error_dict={'perm_new_member_name': msg} - ) - return value - return _ValidPerms - - -class ValidSettings(formencode.validators.FancyValidator): - - def to_python(self, value, state): - # settings form can't edit user - if 'user' in value: - del['value']['user'] - return value - - -class ValidPath(formencode.validators.FancyValidator): - def to_python(self, value, state): - - if not os.path.isdir(value): - msg = _('This is not a valid path') - raise formencode.Invalid(msg, value, state, - error_dict={'paths_root_path': msg}) - return value - - -def UniqSystemEmail(old_data): - class _UniqSystemEmail(formencode.validators.FancyValidator): - def to_python(self, value, state): - value = value.lower() - if (old_data.get('email') or '').lower() != value: - user = User.get_by_email(value, case_insensitive=True) - if user: - raise formencode.Invalid( - _("This e-mail address is already taken"), value, state - ) - return value - - return _UniqSystemEmail - - -class ValidSystemEmail(formencode.validators.FancyValidator): - def to_python(self, value, state): - value = value.lower() - user = User.get_by_email(value, case_insensitive=True) - if user is None: - raise formencode.Invalid( - _("This e-mail address doesn't exist."), value, state - ) - - return value - - -class LdapLibValidator(formencode.validators.FancyValidator): - - def to_python(self, value, state): - - try: - import ldap - except ImportError: - raise LdapImportError - return value - - -class AttrLoginValidator(formencode.validators.FancyValidator): - - def to_python(self, value, state): - - if not value or not isinstance(value, (str, unicode)): - raise formencode.Invalid( - _("The LDAP Login attribute of the CN must be specified - " - "this is the name of the attribute that is equivalent " - "to 'username'"), value, state - ) - - return value - - -#============================================================================== -# FORMS -#============================================================================== class LoginForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - username = UnicodeString( + username = v.UnicodeString( strip=True, min=1, not_empty=True, messages={ - 'empty': _('Please enter a login'), - 'tooShort': _('Enter a value %(min)i characters long or more')} + 'empty': _(u'Please enter a login'), + 'tooShort': _(u'Enter a value %(min)i characters long or more')} ) - password = UnicodeString( + password = v.UnicodeString( strip=False, min=3, not_empty=True, messages={ - 'empty': _('Please enter a password'), - 'tooShort': _('Enter %(min)i characters or more')} + 'empty': _(u'Please enter a password'), + 'tooShort': _(u'Enter %(min)i characters or more')} ) - remember = StringBoolean(if_missing=False) + remember = v.StringBoolean(if_missing=False) - chained_validators = [ValidAuth] + chained_validators = [v.ValidAuth()] def UserForm(edit=False, old_data={}): class _UserForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - username = All(UnicodeString(strip=True, min=1, not_empty=True), - ValidUsername(edit, old_data)) + username = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.ValidUsername(edit, old_data)) if edit: - new_password = All(UnicodeString(strip=False, min=6, not_empty=False)) - password_confirmation = All(UnicodeString(strip=False, min=6, - not_empty=False)) - admin = StringBoolean(if_missing=False) + new_password = All( + v.UnicodeString(strip=False, min=6, not_empty=False) + ) + password_confirmation = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=False), + ) + admin = v.StringBoolean(if_missing=False) else: - password = All(UnicodeString(strip=False, min=6, not_empty=True)) - password_confirmation = All(UnicodeString(strip=False, min=6, - not_empty=False)) + password = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=True) + ) + password_confirmation = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=False) + ) - active = StringBoolean(if_missing=False) - name = UnicodeString(strip=True, min=1, not_empty=False) - lastname = UnicodeString(strip=True, min=1, not_empty=False) - email = All(Email(not_empty=True), UniqSystemEmail(old_data)) + active = v.StringBoolean(if_missing=False) + name = v.UnicodeString(strip=True, min=1, not_empty=False) + lastname = v.UnicodeString(strip=True, min=1, not_empty=False) + email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data)) - chained_validators = [ValidPasswordsMatch, ValidPassword] + chained_validators = [v.ValidPasswordsMatch()] return _UserForm @@ -595,15 +98,18 @@ allow_extra_fields = True filter_extra_fields = True - users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True), - ValidUsersGroup(edit, old_data)) + users_group_name = All( + v.UnicodeString(strip=True, min=1, not_empty=True), + v.ValidUsersGroup(edit, old_data) + ) - users_group_active = StringBoolean(if_missing=False) + users_group_active = v.StringBoolean(if_missing=False) if edit: - users_group_members = OneOf(available_members, hideList=False, - testValueList=True, - if_missing=None, not_empty=False) + users_group_members = v.OneOf( + available_members, hideList=False, testValueList=True, + if_missing=None, not_empty=False + ) return _UsersGroupForm @@ -613,15 +119,16 @@ allow_extra_fields = True filter_extra_fields = False - group_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyName()) - group_description = UnicodeString(strip=True, min=1, + group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.SlugifyName()) + group_description = v.UnicodeString(strip=True, min=1, not_empty=True) - group_parent_id = OneOf(available_groups, hideList=False, + group_parent_id = v.OneOf(available_groups, hideList=False, testValueList=True, if_missing=None, not_empty=False) - chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')] + chained_validators = [v.ValidReposGroup(edit, old_data), + v.ValidPerms('group')] return _ReposGroupForm @@ -630,16 +137,24 @@ class _RegisterForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - username = All(ValidUsername(edit, old_data), - UnicodeString(strip=True, min=1, not_empty=True)) - password = All(UnicodeString(strip=False, min=6, not_empty=True)) - password_confirmation = All(UnicodeString(strip=False, min=6, not_empty=True)) - active = StringBoolean(if_missing=False) - name = UnicodeString(strip=True, min=1, not_empty=False) - lastname = UnicodeString(strip=True, min=1, not_empty=False) - email = All(Email(not_empty=True), UniqSystemEmail(old_data)) + username = All( + v.ValidUsername(edit, old_data), + v.UnicodeString(strip=True, min=1, not_empty=True) + ) + password = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=True) + ) + password_confirmation = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=True) + ) + active = v.StringBoolean(if_missing=False) + name = v.UnicodeString(strip=True, min=1, not_empty=False) + lastname = v.UnicodeString(strip=True, min=1, not_empty=False) + email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data)) - chained_validators = [ValidPasswordsMatch, ValidPassword] + chained_validators = [v.ValidPasswordsMatch()] return _RegisterForm @@ -648,7 +163,7 @@ class _PasswordResetForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - email = All(ValidSystemEmail(), Email(not_empty=True)) + email = All(v.ValidSystemEmail(), v.Email(not_empty=True)) return _PasswordResetForm @@ -657,24 +172,24 @@ class _RepoForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyName()) - clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False)) - repo_group = OneOf(repo_groups, hideList=True) - repo_type = OneOf(supported_backends) - description = UnicodeString(strip=True, min=1, not_empty=False) - private = StringBoolean(if_missing=False) - enable_statistics = StringBoolean(if_missing=False) - enable_downloads = StringBoolean(if_missing=False) - landing_rev = OneOf(landing_revs, hideList=True) + repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.SlugifyName()) + clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False)) + repo_group = v.OneOf(repo_groups, hideList=True) + repo_type = v.OneOf(supported_backends) + description = v.UnicodeString(strip=True, min=1, not_empty=False) + private = v.StringBoolean(if_missing=False) + enable_statistics = v.StringBoolean(if_missing=False) + enable_downloads = v.StringBoolean(if_missing=False) + landing_rev = v.OneOf(landing_revs, hideList=True) if edit: #this is repo owner - user = All(UnicodeString(not_empty=True), ValidRepoUser) + user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser()) - chained_validators = [ValidCloneUri()(), - ValidRepoName(edit, old_data), - ValidPerms()] + chained_validators = [v.ValidCloneUri(), + v.ValidRepoName(edit, old_data), + v.ValidPerms()] return _RepoForm @@ -683,33 +198,34 @@ class _RepoForkForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyName()) - repo_group = OneOf(repo_groups, hideList=True) - repo_type = All(ValidForkType(old_data), OneOf(supported_backends)) - description = UnicodeString(strip=True, min=1, not_empty=True) - private = StringBoolean(if_missing=False) - copy_permissions = StringBoolean(if_missing=False) - update_after_clone = StringBoolean(if_missing=False) - fork_parent_id = UnicodeString() - chained_validators = [ValidForkName(edit, old_data)] + repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.SlugifyName()) + repo_group = v.OneOf(repo_groups, hideList=True) + repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends)) + description = v.UnicodeString(strip=True, min=1, not_empty=True) + private = v.StringBoolean(if_missing=False) + copy_permissions = v.StringBoolean(if_missing=False) + update_after_clone = v.StringBoolean(if_missing=False) + fork_parent_id = v.UnicodeString() + chained_validators = [v.ValidForkName(edit, old_data)] return _RepoForkForm -def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), - repo_groups=[], landing_revs=[]): +def RepoSettingsForm(edit=False, old_data={}, + supported_backends=BACKENDS.keys(), repo_groups=[], + landing_revs=[]): class _RepoForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyName()) - description = UnicodeString(strip=True, min=1, not_empty=True) - repo_group = OneOf(repo_groups, hideList=True) - private = StringBoolean(if_missing=False) - landing_rev = OneOf(landing_revs, hideList=True) - chained_validators = [ValidRepoName(edit, old_data), ValidPerms(), - ValidSettings] + repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.SlugifyName()) + description = v.UnicodeString(strip=True, min=1, not_empty=True) + repo_group = v.OneOf(repo_groups, hideList=True) + private = v.StringBoolean(if_missing=False) + landing_rev = v.OneOf(landing_revs, hideList=True) + chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(), + v.ValidSettings()] return _RepoForm @@ -717,9 +233,9 @@ class _ApplicationSettingsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True) - rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True) - rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False) + rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True) + rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True) + rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False) return _ApplicationSettingsForm @@ -728,12 +244,19 @@ class _ApplicationUiSettingsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - web_push_ssl = OneOf(['true', 'false'], if_missing='false') - paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True)) - hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False) - hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False) - hooks_changegroup_push_logger = OneOf(['True', 'False'], if_missing=False) - hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False) + web_push_ssl = v.OneOf(['true', 'false'], if_missing='false') + paths_root_path = All( + v.ValidPath(), + v.UnicodeString(strip=True, min=1, not_empty=True) + ) + hooks_changegroup_update = v.OneOf(['True', 'False'], + if_missing=False) + hooks_changegroup_repo_size = v.OneOf(['True', 'False'], + if_missing=False) + hooks_changegroup_push_logger = v.OneOf(['True', 'False'], + if_missing=False) + hooks_preoutgoing_pull_logger = v.OneOf(['True', 'False'], + if_missing=False) return _ApplicationUiSettingsForm @@ -742,33 +265,37 @@ class _DefaultPermissionsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - overwrite_default = StringBoolean(if_missing=False) - anonymous = OneOf(['True', 'False'], if_missing=False) - default_perm = OneOf(perms_choices) - default_register = OneOf(register_choices) - default_create = OneOf(create_choices) + overwrite_default = v.StringBoolean(if_missing=False) + anonymous = v.OneOf(['True', 'False'], if_missing=False) + default_perm = v.OneOf(perms_choices) + default_register = v.OneOf(register_choices) + default_create = v.OneOf(create_choices) return _DefaultPermissionsForm -def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices): +def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, + tls_kind_choices): class _LdapSettingsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True #pre_validators = [LdapLibValidator] - ldap_active = StringBoolean(if_missing=False) - ldap_host = UnicodeString(strip=True,) - ldap_port = Number(strip=True,) - ldap_tls_kind = OneOf(tls_kind_choices) - ldap_tls_reqcert = OneOf(tls_reqcert_choices) - ldap_dn_user = UnicodeString(strip=True,) - ldap_dn_pass = UnicodeString(strip=True,) - ldap_base_dn = UnicodeString(strip=True,) - ldap_filter = UnicodeString(strip=True,) - ldap_search_scope = OneOf(search_scope_choices) - ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,)) - ldap_attr_firstname = UnicodeString(strip=True,) - ldap_attr_lastname = UnicodeString(strip=True,) - ldap_attr_email = UnicodeString(strip=True,) + ldap_active = v.StringBoolean(if_missing=False) + ldap_host = v.UnicodeString(strip=True,) + ldap_port = v.Number(strip=True,) + ldap_tls_kind = v.OneOf(tls_kind_choices) + ldap_tls_reqcert = v.OneOf(tls_reqcert_choices) + ldap_dn_user = v.UnicodeString(strip=True,) + ldap_dn_pass = v.UnicodeString(strip=True,) + ldap_base_dn = v.UnicodeString(strip=True,) + ldap_filter = v.UnicodeString(strip=True,) + ldap_search_scope = v.OneOf(search_scope_choices) + ldap_attr_login = All( + v.AttrLoginValidator(), + v.UnicodeString(strip=True,) + ) + ldap_attr_firstname = v.UnicodeString(strip=True,) + ldap_attr_lastname = v.UnicodeString(strip=True,) + ldap_attr_email = v.UnicodeString(strip=True,) return _LdapSettingsForm
--- a/rhodecode/model/notification.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/notification.py Mon Jun 18 00:35:13 2012 +0200 @@ -35,15 +35,13 @@ from rhodecode.lib import helpers as h from rhodecode.model import BaseModel from rhodecode.model.db import Notification, User, UserNotification +from sqlalchemy.orm import joinedload log = logging.getLogger(__name__) class NotificationModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) - def __get_notification(self, notification): if isinstance(notification, Notification): return notification @@ -76,12 +74,12 @@ if recipients and not getattr(recipients, '__iter__', False): raise Exception('recipients must be a list of iterable') - created_by_obj = self.__get_user(created_by) + created_by_obj = self._get_user(created_by) if recipients: recipients_objs = [] for u in recipients: - obj = self.__get_user(u) + obj = self._get_user(u) if obj: recipients_objs.append(obj) recipients_objs = set(recipients_objs) @@ -110,6 +108,7 @@ email_subject = NotificationModel().make_description(notif, False) type_ = type_ email_body = body + ## this is passed into template kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)} kwargs.update(email_kwargs) email_body_html = EmailNotificationModel()\ @@ -124,7 +123,7 @@ # we don't want to remove actual notification just the assignment try: notification = self.__get_notification(notification) - user = self.__get_user(user) + user = self._get_user(user) if notification and user: obj = UserNotification.query()\ .filter(UserNotification.user == user)\ @@ -137,30 +136,56 @@ log.error(traceback.format_exc()) raise - def get_for_user(self, user): - user = self.__get_user(user) - return user.notifications + def get_for_user(self, user, filter_=None): + """ + Get mentions for given user, filter them if filter dict is given + + :param user: + :type user: + :param filter: + """ + user = self._get_user(user) + + q = UserNotification.query()\ + .filter(UserNotification.user == user)\ + .join((Notification, UserNotification.notification_id == + Notification.notification_id)) + + if filter_: + q = q.filter(Notification.type_ == filter_.get('type')) - def mark_all_read_for_user(self, user): - user = self.__get_user(user) - UserNotification.query()\ - .filter(UserNotification.read==False)\ - .update({'read': True}) + return q.all() + + def mark_all_read_for_user(self, user, filter_=None): + user = self._get_user(user) + q = UserNotification.query()\ + .filter(UserNotification.user == user)\ + .filter(UserNotification.read == False)\ + .join((Notification, UserNotification.notification_id == + Notification.notification_id)) + if filter_: + q = q.filter(Notification.type_ == filter_.get('type')) + + # this is a little inefficient but sqlalchemy doesn't support + # update on joined tables :( + for obj in q.all(): + obj.read = True + self.sa.add(obj) def get_unread_cnt_for_user(self, user): - user = self.__get_user(user) + user = self._get_user(user) return UserNotification.query()\ .filter(UserNotification.read == False)\ .filter(UserNotification.user == user).count() def get_unread_for_user(self, user): - user = self.__get_user(user) + user = self._get_user(user) return [x.notification for x in UserNotification.query()\ .filter(UserNotification.read == False)\ .filter(UserNotification.user == user).all()] def get_user_notification(self, user, notification): - user = self.__get_user(user) + user = self._get_user(user) notification = self.__get_notification(notification) return UserNotification.query()\ @@ -172,12 +197,15 @@ Creates a human readable description based on properties of notification object """ - + #alias + _n = notification _map = { - notification.TYPE_CHANGESET_COMMENT: _('commented on commit'), - notification.TYPE_MESSAGE: _('sent message'), - notification.TYPE_MENTION: _('mentioned you'), - notification.TYPE_REGISTRATION: _('registered in RhodeCode') + _n.TYPE_CHANGESET_COMMENT: _('commented on commit'), + _n.TYPE_MESSAGE: _('sent message'), + _n.TYPE_MENTION: _('mentioned you'), + _n.TYPE_REGISTRATION: _('registered in RhodeCode'), + _n.TYPE_PULL_REQUEST: _('opened new pull request'), + _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request') } # action == _map string @@ -199,6 +227,7 @@ TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT TYPE_PASSWORD_RESET = 'passoword_link' TYPE_REGISTRATION = Notification.TYPE_REGISTRATION + TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST TYPE_DEFAULT = 'default' def __init__(self):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/model/pull_request.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.model.pull_reuquest + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + pull request model for RhodeCode + + :created_on: Jun 6, 2012 + :author: marcink + :copyright: (C) 2012-2012 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 binascii +from pylons.i18n.translation import _ + +from rhodecode.lib import helpers as h +from rhodecode.model import BaseModel +from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification +from rhodecode.model.notification import NotificationModel +from rhodecode.lib.utils2 import safe_unicode + +from rhodecode.lib.vcs.utils.hgcompat import discovery + +log = logging.getLogger(__name__) + + +class PullRequestModel(BaseModel): + + def get_all(self, repo): + repo = self._get_repo(repo) + return PullRequest.query().filter(PullRequest.other_repo == repo).all() + + def create(self, created_by, org_repo, org_ref, other_repo, + other_ref, revisions, reviewers, title, description=None): + created_by_user = self._get_user(created_by) + + new = PullRequest() + new.org_repo = self._get_repo(org_repo) + new.org_ref = org_ref + new.other_repo = self._get_repo(other_repo) + new.other_ref = other_ref + new.revisions = revisions + new.title = title + new.description = description + new.author = created_by_user + self.sa.add(new) + + #members + for member in reviewers: + _usr = self._get_user(member) + reviewer = PullRequestReviewers(_usr, new) + self.sa.add(reviewer) + + #notification to reviewers + notif = NotificationModel() + + subject = safe_unicode( + h.link_to( + _('%(user)s wants you to review pull request #%(pr_id)s') % \ + {'user': created_by_user.username, + 'pr_id': new.pull_request_id}, + h.url('pullrequest_show', repo_name=other_repo, + pull_request_id=new.pull_request_id, + qualified=True, + ) + ) + ) + body = description + notif.create(created_by=created_by, subject=subject, body=body, + recipients=reviewers, + type_=Notification.TYPE_PULL_REQUEST,) + + return new + + def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, + discovery_data): + """ + Returns a list of changesets that are incoming from org_repo@org_ref + to other_repo@other_ref + + :param org_repo: + :type org_repo: + :param org_ref: + :type org_ref: + :param other_repo: + :type other_repo: + :param other_ref: + :type other_ref: + :param tmp: + :type tmp: + """ + changesets = [] + #case two independent repos + if org_repo != other_repo: + common, incoming, rheads = discovery_data + + if not incoming: + revs = [] + else: + revs = org_repo._repo.changelog.findmissing(common, rheads) + + for cs in reversed(map(binascii.hexlify, revs)): + changesets.append(org_repo.get_changeset(cs)) + else: + revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1], + other_ref[1])] + from mercurial import scmutil + out = scmutil.revrange(org_repo._repo, revs) + for cs in reversed(out): + changesets.append(org_repo.get_changeset(cs)) + + return changesets + + def _get_discovery(self, org_repo, org_ref, other_repo, other_ref): + """ + Get's mercurial discovery data used to calculate difference between + repos and refs + + :param org_repo: + :type org_repo: + :param org_ref: + :type org_ref: + :param other_repo: + :type other_repo: + :param other_ref: + :type other_ref: + """ + + other = org_repo._repo + repo = other_repo._repo + tip = other[org_ref[1]] + log.debug('Doing discovery for %s@%s vs %s@%s' % ( + org_repo, org_ref, other_repo, other_ref) + ) + log.debug('Filter heads are %s[%s]' % (tip, org_ref[1])) + tmp = discovery.findcommonincoming( + repo=repo, # other_repo we check for incoming + remote=other, # org_repo source for incoming + heads=[tip.node()], + force=False + ) + return tmp + + def get_compare_data(self, org_repo, org_ref, other_repo, other_ref): + """ + Returns a tuple of incomming changesets, and discoverydata cache + + :param org_repo: + :type org_repo: + :param org_ref: + :type org_ref: + :param other_repo: + :type other_repo: + :param other_ref: + :type other_ref: + """ + + if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)): + raise Exception('org_ref must be a two element list/tuple') + + if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)): + raise Exception('other_ref must be a two element list/tuple') + + discovery_data = self._get_discovery(org_repo.scm_instance, + org_ref, + other_repo.scm_instance, + other_ref) + cs_ranges = self._get_changesets(org_repo.scm_instance, + org_ref, + other_repo.scm_instance, + other_ref, + discovery_data) + return cs_ranges, discovery_data
--- a/rhodecode/model/repo.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/repo.py Mon Jun 18 00:35:13 2012 +0200 @@ -48,9 +48,6 @@ class RepoModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) - def __get_users_group(self, users_group): return self._get_instance(UsersGroup, users_group, callback=UsersGroup.get_by_group_name) @@ -59,14 +56,6 @@ return self._get_instance(RepoGroup, repos_group, callback=RepoGroup.get_by_group_name) - def __get_repo(self, repository): - return self._get_instance(Repository, repository, - callback=Repository.get_by_repo_name) - - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) - @LazyProperty def repos_path(self): """ @@ -86,7 +75,7 @@ return repo.scalar() def get_repo(self, repository): - return self.__get_repo(repository) + return self._get_repo(repository) def get_by_repo_name(self, repo_name, cache=False): repo = self.sa.query(Repository)\ @@ -311,7 +300,7 @@ run_task(tasks.create_repo_fork, form_data, cur_user) def delete(self, repo): - repo = self.__get_repo(repo) + repo = self._get_repo(repo) try: self.sa.delete(repo) self.__delete_repo(repo) @@ -328,9 +317,9 @@ :param user: Instance of User, user_id or username :param perm: Instance of Permission, or permission_name """ - user = self.__get_user(user) - repo = self.__get_repo(repo) - permission = self.__get_perm(perm) + user = self._get_user(user) + repo = self._get_repo(repo) + permission = self._get_perm(perm) # check if we have that permission already obj = self.sa.query(UserRepoToPerm)\ @@ -353,8 +342,8 @@ :param user: Instance of User, user_id or username """ - user = self.__get_user(user) - repo = self.__get_repo(repo) + user = self._get_user(user) + repo = self._get_repo(repo) obj = self.sa.query(UserRepoToPerm)\ .filter(UserRepoToPerm.repository == repo)\ @@ -372,9 +361,9 @@ or users group name :param perm: Instance of Permission, or permission_name """ - repo = self.__get_repo(repo) + repo = self._get_repo(repo) group_name = self.__get_users_group(group_name) - permission = self.__get_perm(perm) + permission = self._get_perm(perm) # check if we have that permission already obj = self.sa.query(UsersGroupRepoToPerm)\ @@ -399,7 +388,7 @@ :param group_name: Instance of UserGroup, users_group_id, or users group name """ - repo = self.__get_repo(repo) + repo = self._get_repo(repo) group_name = self.__get_users_group(group_name) obj = self.sa.query(UsersGroupRepoToPerm)\
--- a/rhodecode/model/repo_permission.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/repo_permission.py Mon Jun 18 00:35:13 2012 +0200 @@ -26,28 +26,17 @@ import logging from rhodecode.model import BaseModel -from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, Permission,\ - User, Repository +from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, \ + Permission log = logging.getLogger(__name__) class RepositoryPermissionModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) - - def __get_repo(self, repository): - return self._get_instance(Repository, repository, - callback=Repository.get_by_repo_name) - - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) - def get_user_permission(self, repository, user): - repository = self.__get_repo(repository) - user = self.__get_user(user) + repository = self._get_repo(repository) + user = self._get_user(user) return UserRepoToPerm.query() \ .filter(UserRepoToPerm.user == user) \
--- a/rhodecode/model/repos_group.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/repos_group.py Mon Jun 18 00:35:13 2012 +0200 @@ -39,9 +39,6 @@ class ReposGroupModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) - def __get_users_group(self, users_group): return self._get_instance(UsersGroup, users_group, callback=UsersGroup.get_by_group_name) @@ -50,10 +47,6 @@ return self._get_instance(RepoGroup, repos_group, callback=RepoGroup.get_by_group_name) - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) - @LazyProperty def repos_path(self): """ @@ -206,13 +199,13 @@ log.error(traceback.format_exc()) raise - def delete(self, users_group_id): + def delete(self, repos_group): + repos_group = self.__get_repos_group(repos_group) try: - users_group = RepoGroup.get(users_group_id) - self.sa.delete(users_group) - self.__delete_group(users_group) + self.sa.delete(repos_group) + self.__delete_group(repos_group) except: - log.error(traceback.format_exc()) + log.exception('Error removing repos_group %s' % repos_group) raise def grant_user_permission(self, repos_group, user, perm): @@ -227,8 +220,8 @@ """ repos_group = self.__get_repos_group(repos_group) - user = self.__get_user(user) - permission = self.__get_perm(perm) + user = self._get_user(user) + permission = self._get_perm(perm) # check if we have that permission already obj = self.sa.query(UserRepoGroupToPerm)\ @@ -253,7 +246,7 @@ """ repos_group = self.__get_repos_group(repos_group) - user = self.__get_user(user) + user = self._get_user(user) obj = self.sa.query(UserRepoGroupToPerm)\ .filter(UserRepoGroupToPerm.user == user)\ @@ -274,7 +267,7 @@ """ repos_group = self.__get_repos_group(repos_group) group_name = self.__get_users_group(group_name) - permission = self.__get_perm(perm) + permission = self._get_perm(perm) # check if we have that permission already obj = self.sa.query(UsersGroupRepoGroupToPerm)\
--- a/rhodecode/model/scm.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/scm.py Mon Jun 18 00:35:13 2012 +0200 @@ -44,7 +44,7 @@ action_logger, EmptyChangeset, REMOVED_REPO_PAT from rhodecode.model import BaseModel from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \ - UserFollowing, UserLog, User, RepoGroup + UserFollowing, UserLog, User, RepoGroup, PullRequest log = logging.getLogger(__name__) @@ -321,19 +321,21 @@ return f is not None - def get_followers(self, repo_id): - if not isinstance(repo_id, int): - repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') + def get_followers(self, repo): + repo = self._get_repo(repo) return self.sa.query(UserFollowing)\ - .filter(UserFollowing.follows_repo_id == repo_id).count() + .filter(UserFollowing.follows_repository == repo).count() - def get_forks(self, repo_id): - if not isinstance(repo_id, int): - repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') + def get_forks(self, repo): + repo = self._get_repo(repo) + return self.sa.query(Repository)\ + .filter(Repository.fork == repo).count() - return self.sa.query(Repository)\ - .filter(Repository.fork_id == repo_id).count() + def get_pull_requests(self, repo): + repo = self._get_repo(repo) + return self.sa.query(PullRequest)\ + .filter(PullRequest.other_repo == repo).count() def mark_as_fork(self, repo, fork, user): repo = self.__get_repo(repo)
--- a/rhodecode/model/user.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/user.py Mon Jun 18 00:35:13 2012 +0200 @@ -35,8 +35,8 @@ from rhodecode.model import BaseModel from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \ UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \ - Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\ - UsersGroupRepoGroupToPerm + Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \ + UserEmailMap from rhodecode.lib.exceptions import DefaultUserException, \ UserOwnsReposException @@ -61,13 +61,6 @@ class UserModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) - - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) - def get(self, user_id, cache=False): user = self.sa.query(User) if cache: @@ -76,7 +69,7 @@ return user.get(user_id) def get_user(self, user): - return self.__get_user(user) + return self._get_user(user) def get_by_username(self, username, cache=False, case_insensitive=False): @@ -94,9 +87,12 @@ return User.get_by_api_key(api_key, cache) def create(self, form_data): + from rhodecode.lib.auth import get_crypt_password try: new_user = User() for k, v in form_data.items(): + if k == 'password': + v = get_crypt_password(v) setattr(new_user, k, v) new_user.api_key = generate_api_key(form_data['username']) @@ -272,15 +268,17 @@ raise def update_my_account(self, user_id, form_data): + from rhodecode.lib.auth import get_crypt_password try: 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")) + _("You can't Edit this user since it's" + " crucial for entire application") + ) for k, v in form_data.items(): if k == 'new_password' and v != '': - user.password = v + user.password = get_crypt_password(v) user.api_key = generate_api_key(user.username) else: if k not in ['admin', 'active']: @@ -292,7 +290,7 @@ raise def delete(self, user): - user = self.__get_user(user) + user = self._get_user(user) try: if user.username == 'default': @@ -545,7 +543,7 @@ raise Exception('perm needs to be an instance of Permission class ' 'got %s instead' % type(perm)) - user = self.__get_user(user) + user = self._get_user(user) return UserToPerm.query().filter(UserToPerm.user == user)\ .filter(UserToPerm.permission == perm).scalar() is not None @@ -557,8 +555,8 @@ :param user: :param perm: """ - user = self.__get_user(user) - perm = self.__get_perm(perm) + user = self._get_user(user) + perm = self._get_perm(perm) # if this permission is already granted skip it _perm = UserToPerm.query()\ .filter(UserToPerm.user == user)\ @@ -578,8 +576,8 @@ :param user: :param perm: """ - user = self.__get_user(user) - perm = self.__get_perm(perm) + user = self._get_user(user) + perm = self._get_perm(perm) obj = UserToPerm.query()\ .filter(UserToPerm.user == user)\ @@ -587,3 +585,29 @@ .scalar() if obj: self.sa.delete(obj) + + def add_extra_email(self, user, email): + """ + Adds email address to UserEmailMap + + :param user: + :param email: + """ + user = self._get_user(user) + obj = UserEmailMap() + obj.user = user + obj.email = email + self.sa.add(obj) + return obj + + def delete_extra_email(self, user, email_id): + """ + Removes email address from UserEmailMap + + :param user: + :param email_id: + """ + user = self._get_user(user) + obj = UserEmailMap.query().get(email_id) + if obj: + self.sa.delete(obj) \ No newline at end of file
--- a/rhodecode/model/users_group.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/model/users_group.py Mon Jun 18 00:35:13 2012 +0200 @@ -37,17 +37,10 @@ class UsersGroupModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) - def __get_users_group(self, users_group): return self._get_instance(UsersGroup, users_group, callback=UsersGroup.get_by_group_name) - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) - def get(self, users_group_id, cache=False): return UsersGroup.get(users_group_id) @@ -115,7 +108,7 @@ def add_user_to_group(self, users_group, user): users_group = self.__get_users_group(users_group) - user = self.__get_user(user) + user = self._get_user(user) for m in users_group.members: u = m.user @@ -138,7 +131,7 @@ def remove_user_from_group(self, users_group, user): users_group = self.__get_users_group(users_group) - user = self.__get_user(user) + user = self._get_user(user) users_group_member = None for m in users_group.members: @@ -160,7 +153,7 @@ def has_perm(self, users_group, perm): users_group = self.__get_users_group(users_group) - perm = self.__get_perm(perm) + perm = self._get_perm(perm) return UsersGroupToPerm.query()\ .filter(UsersGroupToPerm.users_group == users_group)\ @@ -187,7 +180,7 @@ def revoke_perm(self, users_group, perm): users_group = self.__get_users_group(users_group) - perm = self.__get_perm(perm) + perm = self._get_perm(perm) obj = UsersGroupToPerm.query()\ .filter(UsersGroupToPerm.users_group == users_group)\
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/model/validators.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,592 @@ +""" +Set of generic validators +""" +import os +import re +import formencode +import logging +from pylons.i18n.translation import _ +from webhelpers.pylonslib.secure_form import authentication_token + +from formencode.validators import ( + UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set +) + +from rhodecode.lib.utils import repo_name_slug +from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User +from rhodecode.lib.auth import authenticate +from rhodecode.lib.exceptions import LdapImportError +from rhodecode.config.routing import ADMIN_PREFIX +# silence warnings and pylint +UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set + +log = logging.getLogger(__name__) + + +class StateObj(object): + """ + this is needed to translate the messages using _() in validators + """ + _ = staticmethod(_) + + +def M(self, key, state=None, **kwargs): + """ + returns string from self.message based on given key, + passed kw params are used to substitute %(named)s params inside + translated strings + + :param msg: + :param state: + """ + if state is None: + state = StateObj() + else: + state._ = staticmethod(_) + #inject validator into state object + return self.message(key, state, **kwargs) + + +def ValidUsername(edit=False, old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'username_exists': _(u'Username "%(username)s" already exists'), + 'system_invalid_username': + _(u'Username "%(username)s" is forbidden'), + 'invalid_username': + _(u'Username may only contain alphanumeric characters ' + 'underscores, periods or dashes and must begin with ' + 'alphanumeric character') + } + + def validate_python(self, value, state): + if value in ['default', 'new_user']: + msg = M(self, 'system_invalid_username', state, username=value) + raise formencode.Invalid(msg, value, state) + #check if user is unique + old_un = None + if edit: + old_un = User.get(old_data.get('user_id')).username + + if old_un != value or not edit: + if User.get_by_username(value, case_insensitive=True): + msg = M(self, 'username_exists', state, username=value) + raise formencode.Invalid(msg, value, state) + + if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: + msg = M(self, 'invalid_username', state) + raise formencode.Invalid(msg, value, state) + return _validator + + +def ValidRepoUser(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_username': _(u'Username %(username)s is not valid') + } + + def validate_python(self, value, state): + try: + User.query().filter(User.active == True)\ + .filter(User.username == value).one() + except Exception: + msg = M(self, 'invalid_username', state, username=value) + raise formencode.Invalid(msg, value, state, + error_dict=dict(username=msg) + ) + + return _validator + + +def ValidUsersGroup(edit=False, old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_group': _(u'Invalid users group name'), + 'group_exist': _(u'Users group "%(usersgroup)s" already exists'), + 'invalid_usersgroup_name': + _(u'users group name may only contain alphanumeric ' + 'characters underscores, periods or dashes and must begin ' + 'with alphanumeric character') + } + + def validate_python(self, value, state): + if value in ['default']: + msg = M(self, 'invalid_group', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(users_group_name=msg) + ) + #check if group is unique + old_ugname = None + if edit: + old_id = old_data.get('users_group_id') + old_ugname = UsersGroup.get(old_id).users_group_name + + if old_ugname != value or not edit: + is_existing_group = UsersGroup.get_by_group_name(value, + case_insensitive=True) + if is_existing_group: + msg = M(self, 'group_exist', state, usersgroup=value) + raise formencode.Invalid(msg, value, state, + error_dict=dict(users_group_name=msg) + ) + + if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: + msg = M(self, 'invalid_usersgroup_name', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(users_group_name=msg) + ) + + return _validator + + +def ValidReposGroup(edit=False, old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'group_parent_id': _(u'Cannot assign this group as parent'), + 'group_exists': _(u'Group "%(group_name)s" already exists'), + 'repo_exists': + _(u'Repository with name "%(group_name)s" already exists') + } + + def validate_python(self, value, state): + # TODO WRITE VALIDATIONS + group_name = value.get('group_name') + group_parent_id = value.get('group_parent_id') + + # slugify repo group just in case :) + slug = repo_name_slug(group_name) + + # check for parent of self + parent_of_self = lambda: ( + old_data['group_id'] == int(group_parent_id) + if group_parent_id else False + ) + if edit and parent_of_self(): + msg = M(self, 'group_parent_id', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(group_parent_id=msg) + ) + + old_gname = None + if edit: + old_gname = RepoGroup.get(old_data.get('group_id')).group_name + + if old_gname != group_name or not edit: + + # check group + gr = RepoGroup.query()\ + .filter(RepoGroup.group_name == slug)\ + .filter(RepoGroup.group_parent_id == group_parent_id)\ + .scalar() + + if gr: + msg = M(self, 'group_exists', state, group_name=slug) + raise formencode.Invalid(msg, value, state, + error_dict=dict(group_name=msg) + ) + + # check for same repo + repo = Repository.query()\ + .filter(Repository.repo_name == slug)\ + .scalar() + + if repo: + msg = M(self, 'repo_exists', state, group_name=slug) + raise formencode.Invalid(msg, value, state, + error_dict=dict(group_name=msg) + ) + + return _validator + + +def ValidPassword(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_password': + _(u'Invalid characters (non-ascii) in password') + } + + def validate_python(self, value, state): + try: + (value or '').decode('ascii') + except UnicodeError: + msg = M(self, 'invalid_password', state) + raise formencode.Invalid(msg, value, state,) + return _validator + + +def ValidPasswordsMatch(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'password_mismatch': _(u'Passwords do not match'), + } + + def validate_python(self, value, state): + + pass_val = value.get('password') or value.get('new_password') + if pass_val != value['password_confirmation']: + msg = M(self, 'password_mismatch', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(password_confirmation=msg) + ) + return _validator + + +def ValidAuth(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_password': _(u'invalid password'), + 'invalid_username': _(u'invalid user name'), + 'disabled_account': _(u'Your account is disabled') + } + + def validate_python(self, value, state): + password = value['password'] + username = value['username'] + + if not authenticate(username, password): + user = User.get_by_username(username) + if user and user.active is False: + log.warning('user %s is disabled' % username) + msg = M(self, 'disabled_account', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(username=msg) + ) + else: + log.warning('user %s failed to authenticate' % username) + msg = M(self, 'invalid_username', state) + msg2 = M(self, 'invalid_password', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(username=msg, password=msg2) + ) + return _validator + + +def ValidAuthToken(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_token': _(u'Token mismatch') + } + + def validate_python(self, value, state): + if value != authentication_token(): + msg = M(self, 'invalid_token', state) + raise formencode.Invalid(msg, value, state) + return _validator + + +def ValidRepoName(edit=False, old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_repo_name': + _(u'Repository name %(repo)s is disallowed'), + 'repository_exists': + _(u'Repository named %(repo)s already exists'), + 'repository_in_group_exists': _(u'Repository "%(repo)s" already ' + 'exists in group "%(group)s"'), + 'same_group_exists': _(u'Repositories group with name "%(repo)s" ' + 'already exists') + } + + def _to_python(self, value, state): + repo_name = repo_name_slug(value.get('repo_name', '')) + repo_group = value.get('repo_group') + if repo_group: + gr = RepoGroup.get(repo_group) + group_path = gr.full_path + group_name = gr.group_name + # value needs to be aware of group name in order to check + # db key This is an actual just the name to store in the + # database + repo_name_full = group_path + RepoGroup.url_sep() + repo_name + else: + group_name = group_path = '' + repo_name_full = repo_name + + value['repo_name'] = repo_name + value['repo_name_full'] = repo_name_full + value['group_path'] = group_path + value['group_name'] = group_name + return value + + def validate_python(self, value, state): + + repo_name = value.get('repo_name') + repo_name_full = value.get('repo_name_full') + group_path = value.get('group_path') + group_name = value.get('group_name') + + if repo_name in [ADMIN_PREFIX, '']: + msg = M(self, 'invalid_repo_name', state, repo=repo_name) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_name=msg) + ) + + rename = old_data.get('repo_name') != repo_name_full + create = not edit + if rename or create: + + if group_path != '': + if Repository.get_by_repo_name(repo_name_full): + msg = M(self, 'repository_in_group_exists', state, + repo=repo_name, group=group_name) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_name=msg) + ) + elif RepoGroup.get_by_group_name(repo_name_full): + msg = M(self, 'same_group_exists', state, + repo=repo_name) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_name=msg) + ) + + elif Repository.get_by_repo_name(repo_name_full): + msg = M(self, 'repository_exists', state, + repo=repo_name) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_name=msg) + ) + return value + return _validator + + +def ValidForkName(*args, **kwargs): + return ValidRepoName(*args, **kwargs) + + +def SlugifyName(): + class _validator(formencode.validators.FancyValidator): + + def _to_python(self, value, state): + return repo_name_slug(value) + + def validate_python(self, value, state): + pass + + return _validator + + +def ValidCloneUri(): + from rhodecode.lib.utils import make_ui + + def url_handler(repo_type, url, proto, ui=None): + if repo_type == 'hg': + from mercurial.httprepo import httprepository, httpsrepository + if proto == 'https': + httpsrepository(make_ui('db'), url).capabilities + elif proto == 'http': + httprepository(make_ui('db'), url).capabilities + elif repo_type == 'git': + #TODO: write a git url validator + pass + + class _validator(formencode.validators.FancyValidator): + messages = { + 'clone_uri': _(u'invalid clone url'), + 'invalid_clone_uri': _(u'Invalid clone url, provide a ' + 'valid clone http\s url') + } + + def validate_python(self, value, state): + repo_type = value.get('repo_type') + url = value.get('clone_uri') + + if not url: + pass + elif url.startswith('https') or url.startswith('http'): + _type = 'https' if url.startswith('https') else 'http' + try: + url_handler(repo_type, url, _type, make_ui('db')) + except Exception: + log.exception('Url validation failed') + msg = M(self, 'clone_uri') + raise formencode.Invalid(msg, value, state, + error_dict=dict(clone_uri=msg) + ) + else: + msg = M(self, 'invalid_clone_uri', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(clone_uri=msg) + ) + return _validator + + +def ValidForkType(old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_fork_type': _(u'Fork have to be the same type as parent') + } + + def validate_python(self, value, state): + if old_data['repo_type'] != value: + msg = M(self, 'invalid_fork_type', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_type=msg) + ) + return _validator + + +def ValidPerms(type_='repo'): + if type_ == 'group': + EMPTY_PERM = 'group.none' + elif type_ == 'repo': + EMPTY_PERM = 'repository.none' + + class _validator(formencode.validators.FancyValidator): + messages = { + 'perm_new_member_name': + _(u'This username or users group name is not valid') + } + + def to_python(self, value, state): + perms_update = [] + perms_new = [] + # build a list of permission to update and new permission to create + for k, v in value.items(): + # means new added member to permissions + if k.startswith('perm_new_member'): + new_perm = value.get('perm_new_member', False) + new_member = value.get('perm_new_member_name', False) + new_type = value.get('perm_new_member_type') + + if new_member and new_perm: + if (new_member, new_perm, new_type) not in perms_new: + perms_new.append((new_member, new_perm, new_type)) + elif k.startswith('u_perm_') or k.startswith('g_perm_'): + member = k[7:] + t = {'u': 'user', + 'g': 'users_group' + }[k[0]] + if member == 'default': + if value.get('private'): + # set none for default when updating to + # private repo + v = EMPTY_PERM + perms_update.append((member, v, t)) + + value['perms_updates'] = perms_update + value['perms_new'] = perms_new + + # update permissions + for k, v, t in perms_new: + try: + if t is 'user': + self.user_db = User.query()\ + .filter(User.active == True)\ + .filter(User.username == k).one() + if t is 'users_group': + self.user_db = UsersGroup.query()\ + .filter(UsersGroup.users_group_active == True)\ + .filter(UsersGroup.users_group_name == k).one() + + except Exception: + log.exception('Updated permission failed') + msg = M(self, 'perm_new_member_type', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(perm_new_member_name=msg) + ) + return value + return _validator + + +def ValidSettings(): + class _validator(formencode.validators.FancyValidator): + def _to_python(self, value, state): + # settings form can't edit user + if 'user' in value: + del value['user'] + return value + + def validate_python(self, value, state): + pass + return _validator + + +def ValidPath(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_path': _(u'This is not a valid path') + } + + def validate_python(self, value, state): + if not os.path.isdir(value): + msg = M(self, 'invalid_path', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(paths_root_path=msg) + ) + return _validator + + +def UniqSystemEmail(old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'email_taken': _(u'This e-mail address is already taken') + } + + def _to_python(self, value, state): + return value.lower() + + def validate_python(self, value, state): + if (old_data.get('email') or '').lower() != value: + user = User.get_by_email(value, case_insensitive=True) + if user: + msg = M(self, 'email_taken', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(email=msg) + ) + return _validator + + +def ValidSystemEmail(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'non_existing_email': _(u'e-mail "%(email)s" does not exist.') + } + + def _to_python(self, value, state): + return value.lower() + + def validate_python(self, value, state): + user = User.get_by_email(value, case_insensitive=True) + if user is None: + msg = M(self, 'non_existing_email', state, email=value) + raise formencode.Invalid(msg, value, state, + error_dict=dict(email=msg) + ) + + return _validator + + +def LdapLibValidator(): + class _validator(formencode.validators.FancyValidator): + messages = { + + } + + def validate_python(self, value, state): + try: + import ldap + ldap # pyflakes silence ! + except ImportError: + raise LdapImportError() + + return _validator + + +def AttrLoginValidator(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_cn': + _(u'The LDAP Login attribute of the CN must be specified - ' + 'this is the name of the attribute that is equivalent ' + 'to "username"') + } + + def validate_python(self, value, state): + if not value or not isinstance(value, (str, unicode)): + msg = M(self, 'invalid_cn', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(ldap_attr_login=msg) + ) + + return _validator
--- a/rhodecode/public/css/style.css Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/public/css/style.css Mon Jun 18 00:35:13 2012 +0200 @@ -2352,7 +2352,7 @@ padding: 2px 0px 2px 0px; } -.cs_files .cs_added { +.cs_files .cs_added,.cs_files .cs_A { background: url("../images/icons/page_white_add.png") no-repeat scroll 3px; height: 16px; @@ -2361,7 +2361,7 @@ text-align: left; } -.cs_files .cs_changed { +.cs_files .cs_changed,.cs_files .cs_M { background: url("../images/icons/page_white_edit.png") no-repeat scroll 3px; height: 16px; @@ -2370,7 +2370,7 @@ text-align: left; } -.cs_files .cs_removed { +.cs_files .cs_removed,.cs_files .cs_D { background: url("../images/icons/page_white_delete.png") no-repeat scroll 3px; height: 16px; @@ -2466,6 +2466,31 @@ font-weight: bold !important; } +.changeset-status-container{ + padding-right: 5px; + margin-top:1px; + float:right; + height:14px; +} +.code-header .changeset-status-container{ + float:left; + padding:2px 0px 0px 2px; +} +.changeset-status-container .changeset-status-lbl{ + color: rgb(136, 136, 136); + float: left; + padding: 3px 4px 0px 0px +} +.code-header .changeset-status-container .changeset-status-lbl{ + float: left; + padding: 0px 4px 0px 0px; +} +.changeset-status-container .changeset-status-ico{ + float: left; +} +.code-header .changeset-status-container .changeset-status-ico, .container .changeset-status-ico{ + float: left; +} .right .comments-container{ padding-right: 5px; margin-top:1px; @@ -3713,6 +3738,21 @@ padding:0px 0px 0px 10px; } +.emails_wrap{ + padding: 0px 20px; +} + +.emails_wrap .email_entry{ + height: 30px; + padding:0px 0px 0px 10px; +} +.emails_wrap .email_entry .email{ + float: left +} +.emails_wrap .email_entry .email_action{ + float: left +} + /*README STYLE*/ div.readme { @@ -3917,6 +3957,7 @@ background: #f8f8f8; padding: 4px; border-bottom: 1px solid #ddd; + height: 18px; } .comments .comment .meta img { @@ -3925,9 +3966,13 @@ .comments .comment .meta .user { font-weight: bold; + float: left; + padding: 4px 2px 2px 2px; } .comments .comment .meta .date { + float: left; + padding:4px 4px 0px 4px; } .comments .comment .text { @@ -3946,6 +3991,11 @@ /** comment form **/ +.status-block{ + height:80px; + clear:both +} + .comment-form .clearfix{ background: #EEE; -webkit-border-radius: 4px; @@ -4115,6 +4165,7 @@ background: #f8f8f8; padding: 4px; border-bottom: 1px solid #ddd; + height: 20px; } .inline-comments .comment .meta img { @@ -4123,9 +4174,13 @@ .inline-comments .comment .meta .user { font-weight: bold; + float:left; + padding: 3px; } .inline-comments .comment .meta .date { + float:left; + padding: 3px; } .inline-comments .comment .text {
--- a/rhodecode/public/js/rhodecode.js Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/public/js/rhodecode.js Mon Jun 18 00:35:13 2012 +0200 @@ -205,7 +205,7 @@ success:s_wrapper, failure:function(o){ console.log(o); - YUD.get(container).innerHTML='ERROR'; + YUD.get(container).innerHTML='ERROR '+o.status; YUD.setStyle(container,'opacity','1.0'); YUD.setStyle(container,'color','red'); } @@ -1326,4 +1326,142 @@ var comp = YAHOO.util.Sort.compare; var compState = comp(a_, b_, desc); return compState; -}; \ No newline at end of file +}; + + + +/* Multi selectors */ + +var MultiSelectWidget = function(selected_id, available_id, form_id){ + + + //definition of containers ID's + var selected_container = selected_id; + var available_container = available_id; + + //temp container for selected storage. + var cache = new Array(); + var av_cache = new Array(); + var c = YUD.get(selected_container); + var ac = YUD.get(available_container); + + //get only selected options for further fullfilment + for(var i = 0;node =c.options[i];i++){ + if(node.selected){ + //push selected to my temp storage left overs :) + cache.push(node); + } + } + + //get all available options to cache + for(var i = 0;node =ac.options[i];i++){ + //push selected to my temp storage left overs :) + av_cache.push(node); + } + + //fill available only with those not in choosen + ac.options.length=0; + tmp_cache = new Array(); + + for(var i = 0;node = av_cache[i];i++){ + var add = true; + for(var i2 = 0;node_2 = cache[i2];i2++){ + if(node.value == node_2.value){ + add=false; + break; + } + } + if(add){ + tmp_cache.push(new Option(node.text, node.value, false, false)); + } + } + + for(var i = 0;node = tmp_cache[i];i++){ + ac.options[i] = node; + } + + function prompts_action_callback(e){ + + var choosen = YUD.get(selected_container); + var available = YUD.get(available_container); + + //get checked and unchecked options from field + function get_checked(from_field){ + //temp container for storage. + var sel_cache = new Array(); + var oth_cache = new Array(); + + for(var i = 0;node = from_field.options[i];i++){ + if(node.selected){ + //push selected fields :) + sel_cache.push(node); + } + else{ + oth_cache.push(node) + } + } + + return [sel_cache,oth_cache] + } + + //fill the field with given options + function fill_with(field,options){ + //clear firtst + field.options.length=0; + for(var i = 0;node = options[i];i++){ + field.options[i]=new Option(node.text, node.value, + false, false); + } + + } + //adds to current field + function add_to(field,options){ + for(var i = 0;node = options[i];i++){ + field.appendChild(new Option(node.text, node.value, + false, false)); + } + } + + // add action + if (this.id=='add_element'){ + var c = get_checked(available); + add_to(choosen,c[0]); + fill_with(available,c[1]); + } + // remove action + if (this.id=='remove_element'){ + var c = get_checked(choosen); + add_to(available,c[0]); + fill_with(choosen,c[1]); + } + // add all elements + if(this.id=='add_all_elements'){ + for(var i=0; node = available.options[i];i++){ + choosen.appendChild(new Option(node.text, + node.value, false, false)); + } + available.options.length = 0; + } + //remove all elements + if(this.id=='remove_all_elements'){ + for(var i=0; node = choosen.options[i];i++){ + available.appendChild(new Option(node.text, + node.value, false, false)); + } + choosen.options.length = 0; + } + + } + + YUE.addListener(['add_element','remove_element', + 'add_all_elements','remove_all_elements'],'click', + prompts_action_callback) + if (form_id !== undefined) { + YUE.addListener(form_id,'submit',function(){ + var choosen = YUD.get(selected_container); + for (var i = 0; i < choosen.options.length; i++) { + choosen.options[i].selected = 'selected'; + } + }); + } +} \ No newline at end of file
--- a/rhodecode/templates/admin/notifications/notifications.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/admin/notifications/notifications.html Mon Jun 18 00:35:13 2012 +0200 @@ -25,6 +25,10 @@ ##</ul> </div> %if c.notifications: + <div style="padding:14px 18px;text-align: right;float:left"> + <span id='all' class="ui-btn"><a href="${h.url.current()}">${_('All')}</a></span> + <span id='pull_request' class="ui-btn"><a href="${h.url.current(type=c.pull_request_type)}">${_('Pull requests')}</a></span> + </div> <div style="padding:14px 18px;text-align: right;float:right"> <span id='mark_all_read' class="ui-btn">${_('Mark all read')}</span> </div> @@ -40,7 +44,7 @@ deleteNotification(url_del,notification_id) }) YUE.on('mark_all_read','click',function(e){ - var url = "${h.url('notifications_mark_all_read')}"; + var url = "${h.url('notifications_mark_all_read', **request.GET)}"; ypjax(url,'notification_data',function(){ var notification_counter = YUD.get('notification_counter'); if(notification_counter){
--- a/rhodecode/templates/admin/users/user_edit.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/admin/users/user_edit.html Mon Jun 18 00:35:13 2012 +0200 @@ -204,4 +204,48 @@ %endfor </div> </div> +<div class="box box-right"> + <!-- box / title --> + <div class="title"> + <h5>${_('Email addresses')}</h5> + </div> + + <div class="emails_wrap"> + <table class="noborder"> + %for em in c.user_email_map: + <tr> + <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td> + <td><div class="email">${em.email}</div></td> + <td> + ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')} + ${h.hidden('del_email',em.email_id)} + ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id, + class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")} + ${h.end_form()} + </td> + </tr> + %endfor + </table> + </div> + + ${h.form(url('user_emails', id=c.user.user_id),method='put')} + <div class="form"> + <!-- fields --> + <div class="fields"> + <div class="field"> + <div class="label"> + <label for="email">${_('New email address')}:</label> + </div> + <div class="input"> + ${h.text('new_email', class_='medium')} + </div> + </div> + <div class="buttons"> + ${h.submit('save',_('Add'),class_="ui-button")} + ${h.reset('reset',_('Reset'),class_="ui-button")} + </div> + </div> + </div> + ${h.end_form()} +</div> </%def>
--- a/rhodecode/templates/admin/users_groups/users_group_edit.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/admin/users_groups/users_group_edit.html Mon Jun 18 00:35:13 2012 +0200 @@ -140,141 +140,6 @@ </div> </div> <script type="text/javascript"> -YAHOO.util.Event.onDOMReady(function(){ - var D = YAHOO.util.Dom; - var E = YAHOO.util.Event; - - //definition of containers ID's - var available_container = 'available_members'; - var selected_container = 'users_group_members'; - - //form containing containers id - var form_id = 'edit_users_group'; - - //temp container for selected storage. - var cache = new Array(); - var av_cache = new Array(); - var c = D.get(selected_container); - var ac = D.get(available_container); - - //get only selected options for further fullfilment - for(var i = 0;node =c.options[i];i++){ - if(node.selected){ - //push selected to my temp storage left overs :) - cache.push(node); - } - } - - //get all available options to cache - for(var i = 0;node =ac.options[i];i++){ - //push selected to my temp storage left overs :) - av_cache.push(node); - } - - //fill available only with those not in choosen - ac.options.length=0; - tmp_cache = new Array(); - - for(var i = 0;node = av_cache[i];i++){ - var add = true; - for(var i2 = 0;node_2 = cache[i2];i2++){ - if(node.value == node_2.value){ - add=false; - break; - } - } - if(add){ - tmp_cache.push(new Option(node.text, node.value, false, false)); - } - } - - for(var i = 0;node = tmp_cache[i];i++){ - ac.options[i] = node; - } - - function prompts_action_callback(e){ - - var choosen = D.get(selected_container); - var available = D.get(available_container); - - //get checked and unchecked options from field - function get_checked(from_field){ - //temp container for storage. - var sel_cache = new Array(); - var oth_cache = new Array(); - - for(var i = 0;node = from_field.options[i];i++){ - if(node.selected){ - //push selected fields :) - sel_cache.push(node); - } - else{ - oth_cache.push(node) - } - } - - return [sel_cache,oth_cache] - } - - //fill the field with given options - function fill_with(field,options){ - //clear firtst - field.options.length=0; - for(var i = 0;node = options[i];i++){ - field.options[i]=new Option(node.text, node.value, - false, false); - } - - } - //adds to current field - function add_to(field,options){ - for(var i = 0;node = options[i];i++){ - field.appendChild(new Option(node.text, node.value, - false, false)); - } - } - - // add action - if (this.id=='add_element'){ - var c = get_checked(available); - add_to(choosen,c[0]); - fill_with(available,c[1]); - } - // remove action - if (this.id=='remove_element'){ - var c = get_checked(choosen); - add_to(available,c[0]); - fill_with(choosen,c[1]); - } - // add all elements - if(this.id=='add_all_elements'){ - for(var i=0; node = available.options[i];i++){ - choosen.appendChild(new Option(node.text, - node.value, false, false)); - } - available.options.length = 0; - } - //remove all elements - if(this.id=='remove_all_elements'){ - for(var i=0; node = choosen.options[i];i++){ - available.appendChild(new Option(node.text, - node.value, false, false)); - } - choosen.options.length = 0; - } - - } - - E.addListener(['add_element','remove_element', - 'add_all_elements','remove_all_elements'],'click', - prompts_action_callback) - - E.addListener(form_id,'submit',function(){ - var choosen = D.get(selected_container); - for (var i = 0; i < choosen.options.length; i++) { - choosen.options[i].selected = 'selected'; - } - }); -}); + MultiSelectWidget('users_group_members','available_members','edit_users_group'); </script> </%def>
--- a/rhodecode/templates/base/base.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/base/base.html Mon Jun 18 00:35:13 2012 +0200 @@ -247,6 +247,14 @@ <span class="short">${c.repository_forks}</span> </a> </li> + <li> + <a class="menu_link" title="${_('Pull requests')}" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}"> + <span class="icon_short"> + <img src="${h.url('/images/icons/arrow_join.png')}" alt="${_('Pull requests')}" /> + </span> + <span class="short">${c.repository_pull_requests}</span> + </a> + </li> ${usermenu()} </ul> <script type="text/javascript">
--- a/rhodecode/templates/branches/branches.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/branches/branches.html Mon Jun 18 00:35:13 2012 +0200 @@ -25,12 +25,27 @@ ${self.breadcrumbs()} </div> <!-- end box / title --> + %if c.repo_branches: + <div class="info_box" id="compare_branches" style="clear: both;padding: 10px 19px;vertical-align: right;text-align: right;"><a href="#" class="ui-btn small">${_('Compare branches')}</a></div> + %endif <div class="table"> <%include file='branches_data.html'/> </div> </div> <script type="text/javascript"> +YUE.on('compare_branches','click',function(e){ + YUE.preventDefault(e); + var org = YUQ('input[name=compare_org]:checked')[0]; + var other = YUQ('input[name=compare_other]:checked')[0]; + if(org && other){ + var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref='__ORG__',other_ref_type='branch',other_ref='__OTHER__')}"; + var u = compare_url.replace('__ORG__',org.value) + .replace('__OTHER__',other.value); + window.location=u; + } + +}) // main table sorting var myColumnDefs = [ {key:"name",label:"${_('Name')}",sortable:true}, @@ -39,6 +54,7 @@ {key:"author",label:"${_('Author')}",sortable:true}, {key:"revision",label:"${_('Revision')}",sortable:true, sortOptions: { sortFunction: revisionSort }}, + {key:"compare",label:"${_('Compare')}",sortable:false,}, ]; var myDataSource = new YAHOO.util.DataSource(YUD.get("branches_data")); @@ -51,6 +67,7 @@ {key:"date"}, {key:"author"}, {key:"revision"}, + {key:"compare"}, ] };
--- a/rhodecode/templates/branches/branches_data.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/branches/branches_data.html Mon Jun 18 00:35:13 2012 +0200 @@ -7,6 +7,7 @@ <th class="left">${_('date')}</th> <th class="left">${_('author')}</th> <th class="left">${_('revision')}</th> + <th class="left">${_('compare')}</th> </tr> </thead> %for cnt,branch in enumerate(c.repo_branches.items()): @@ -24,6 +25,10 @@ <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre> </div> </td> + <td> + <input class="branch-compare" type="radio" name="compare_org" value="${branch[0]}"/> + <input class="branch-compare" type="radio" name="compare_other" value="${branch[0]}"/> + </td> </tr> %endfor % if hasattr(c,'repo_closed_branches') and c.repo_closed_branches: @@ -42,6 +47,7 @@ <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre> </div> </td> + <td></td> </tr> %endfor %endif
--- a/rhodecode/templates/changelog/changelog.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/changelog/changelog.html Mon Jun 18 00:35:13 2012 +0200 @@ -32,6 +32,12 @@ <canvas id="graph_canvas"></canvas> </div> <div id="graph_content"> + <div class="info_box" style="clear: both;padding: 10px 6px;vertical-align: right;text-align: right;"> + %if c.rhodecode_db_repo.fork: + <a title="${_('compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref='default',other_ref_type='branch',other_ref='default',repo=c.rhodecode_db_repo.fork.repo_name)}" class="ui-btn small">${_('Compare fork')}</a> + %endif + <a href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a> + </div> <div class="container_header"> ${h.form(h.url.current(),method='get')} <div class="info_box" style="float:left"> @@ -76,6 +82,18 @@ </div> %endif </div> + <div class="changeset-status-container"> + %if c.statuses.get(cs.raw_id): + <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div> + <div class="changeset-status-ico"> + %if c.statuses.get(cs.raw_id)[2]: + <a class="tooltip" title="${_('Click to open associated pull request')}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a> + %else: + <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /> + %endif + </div> + %endif + </div> </div> %if cs.parents: %for p_cs in reversed(cs.parents):
--- a/rhodecode/templates/changelog/changelog_details.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/changelog/changelog_details.html Mon Jun 18 00:35:13 2012 +0200 @@ -1,3 +1,5 @@ +## small box that displays changed/added/removed details fetched by AJAX + % if len(c.cs.affected_files) <= c.affected_files_cut_off: <span class="removed tooltip" title="<b>${h.tooltip(_('removed'))}</b>${h.changed_tooltip(c.cs.removed)}">${len(c.cs.removed)}</span> <span class="changed tooltip" title="<b>${h.tooltip(_('changed'))}</b>${h.changed_tooltip(c.cs.changed)}">${len(c.cs.changed)}</span>
--- a/rhodecode/templates/changeset/changeset.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/changeset/changeset.html Mon Jun 18 00:35:13 2012 +0200 @@ -33,6 +33,12 @@ <div class="date"> ${h.fmt_date(c.changeset.date)} </div> + <div class="changeset-status-container"> + %if c.statuses: + <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div> + <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div> + %endif + </div> <div class="diff-actions"> <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a> <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a> @@ -128,8 +134,10 @@ <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> ${comment.comment_inline_form(c.changeset)} - ## render comments - ${comment.comments(c.changeset)} + ## render comments main comments form and it status + ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id), + h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))} + <script type="text/javascript"> YUE.onDOMReady(function(){ AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}"; @@ -159,6 +167,7 @@ // inject comments into they proper positions var file_comments = YUQ('.inline-comment-placeholder'); renderInlineComments(file_comments); + }) </script>
--- a/rhodecode/templates/changeset/changeset_file_comment.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/changeset/changeset_file_comment.html Mon Jun 18 00:35:13 2012 +0200 @@ -7,17 +7,24 @@ <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}"> <div class="comment-wrapp"> <div class="meta"> - <span class="user"> - <img src="${h.gravatar_url(co.author.email, 20)}" /> + <div style="float:left"> <img src="${h.gravatar_url(co.author.email, 20)}" /> </div> + <div class="user"> ${co.author.username} - </span> - <span class="date"> + </div> + <div class="date"> ${h.age(co.modified_at)} - </span> + </div> + %if co.status_change: + <div style="float:left" class="changeset-status-container"> + <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">›</span></div> + <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change.status_lbl}</div> + <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change.status))}" /></div> + </div> + %endif %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id: - <span class="buttons"> + <div class="buttons"> <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span> - </span> + </div> %endif </div> <div class="text"> @@ -70,7 +77,8 @@ </%def> -<%def name="inlines(changeset)"> +## generates inlines taken from c.comments var +<%def name="inlines()"> <div class="comments-number">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div> %for path, lines in c.inline_comments: % for line,comments in lines.iteritems(): @@ -84,11 +92,13 @@ </%def> -<%def name="comments(changeset)"> +## MAIN COMMENT FORM +<%def name="comments(post_url, cur_status)"> <div class="comments"> <div id="inline-comments-container"> - ${inlines(changeset)} + ## generate inlines for this changeset + ${inlines()} </div> %for co in c.comments: @@ -98,16 +108,26 @@ %endfor %if c.rhodecode_user.username != 'default': <div class="comment-form ac"> - ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))} + ${h.form(post_url)} <strong>${_('Leave a comment')}</strong> <div class="clearfix"> <div class="comment-help"> ${(_('Comments parsed using %s syntax with %s support.') % (('<a href="%s">RST</a>' % h.url('rst_help')), '<span style="color:#003367" class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')))|n} + | <span class="tooltip" title="${_('Check this to change current status of code-review for this changeset')}"> ${_('change status')} + <input style="vertical-align: bottom;margin-bottom:-2px" id="show_changeset_status_box" type="checkbox" name="change_changeset_status" /> + </span> </div> - <div class="mentions-container" id="mentions_container"></div> - ${h.textarea('text')} + <div id="status_block_container" class="status-block" style="display:none"> + %for status,lbl in c.changeset_statuses: + <div class=""> + <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" name="changeset_status" value="${status}"> <label>${lbl}</label> + </div> + %endfor + </div> + <div class="mentions-container" id="mentions_container"></div> + ${h.textarea('text')} </div> <div class="comment-button"> ${h.submit('save', _('Comment'), class_='ui-button')} @@ -119,6 +139,17 @@ <script> YUE.onDOMReady(function () { MentionsAutoComplete('text', 'mentions_container', _USERS_AC_DATA, _GROUPS_AC_DATA); + + // changeset status box listener + YUE.on(YUD.get('show_changeset_status_box'),'change',function(e){ + if(e.currentTarget.checked){ + YUD.setStyle('status_block_container','display',''); + } + else{ + YUD.setStyle('status_block_container','display','none'); + } + }) + }); </script> </%def>
--- a/rhodecode/templates/changeset/changeset_range.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/changeset/changeset_range.html Mon Jun 18 00:35:13 2012 +0200 @@ -35,13 +35,18 @@ <div id="changeset_compare_view_content"> <div class="container"> <table class="compare_view_commits noborder"> - %for cs in c.cs_ranges: + %for cnt,cs in enumerate(c.cs_ranges): <tr> <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td> <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td> <td><div class="author">${h.person(cs.author)}</div></td> - <td><span class="tooltip" title="${h.tooltip(h.age(cs.date))}">${h.fmt_date(cs.date)}</span></td> - <td><div class="message">${h.urlify_commit(cs.message, c.repo_name)}</div></td> + <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td> + <td> + %if c.statuses: + <div title="${h.tooltip(_('Changeset status'))}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cnt])}" /></div> + %endif + </td> + <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td> </tr> %endfor </table>
--- a/rhodecode/templates/changeset/diff_block.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/changeset/diff_block.html Mon Jun 18 00:35:13 2012 +0200 @@ -1,12 +1,12 @@ ## -*- coding: utf-8 -*- ##usage: ## <%namespace name="diff_block" file="/changeset/diff_block.html"/> -## ${diff_block.diff_block(changes)} +## ${diff_block.diff_block(change)} ## -<%def name="diff_block(changes)"> +<%def name="diff_block(change)"> -%for change,filenode,diff,cs1,cs2,stat in changes: - %if change !='removed': +%for op,filenode,diff,cs1,cs2,stat in change: + %if op !='removed': <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}_target" style="clear:both;margin-top:25px"></div> <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" class="diffblock margined comm"> <div class="code-header"> @@ -39,3 +39,23 @@ %endfor </%def> + +<%def name="diff_block_simple(change)"> + + %for op,filenode_path,diff in change: + <div id="${h.FID('',filenode_path)}_target" style="clear:both;margin-top:25px"></div> + <div id="${h.FID('',filenode_path)}" class="diffblock margined comm"> + <div class="code-header"> + <div class="changeset_header"> + <div class="changeset_file"> + <a href="#">${h.safe_unicode(filenode_path)}</a> + </div> + </div> + </div> + <div class="code-body"> + <div class="full_f_path" path="${h.safe_unicode(filenode_path)}"></div> + ${diff|n} + </div> + </div> + %endfor +</%def> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/compare/compare_cs.html Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,27 @@ +## Changesets table ! +<div class="container"> + <table class="compare_view_commits noborder"> + %if not c.cs_ranges: + <tr><td>${_('No changesets')}</td></tr> + %else: + %for cnt, cs in enumerate(c.cs_ranges): + <tr> + <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td> + <td> + %if cs.raw_id in c.statuses: + <div title="${c.statuses[cs.raw_id][1]}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" /></div> + %endif + </td> + <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))} + %if c.as_form: + ${h.hidden('revisions',cs.raw_id)} + %endif + </td> + <td><div class="author">${h.person(cs.author)}</div></td> + <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td> + <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td> + </tr> + %endfor + %endif + </table> +</div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/compare/compare_diff.html Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,77 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${c.repo_name} ${_('Compare')} ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} +</%def> + +<%def name="breadcrumbs_links()"> + ${h.link_to(u'Home',h.url('/'))} + » + ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} + » + ${_('Compare')} +</%def> + +<%def name="page_nav()"> + ${self.menu('changelog')} +</%def> + +<%def name="main()"> +<div class="box"> + <!-- box / title --> + <div class="title"> + ${self.breadcrumbs()} + </div> + <div class="table"> + <div id="body" class="diffblock"> + <div class="code-header cv"> + <h3 class="code-header-title">${_('Compare View')}</h3> + <div> + ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} <a href="${c.swap_url}">[swap]</a> + </div> + </div> + </div> + <div id="changeset_compare_view_content"> + ##CS + <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Outgoing changesets')}</div> + <%include file="compare_cs.html" /> + + ## FILES + <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div> + <div class="cs_files"> + %for fid, change, f, stat in c.files: + <div class="cs_${change}"> + <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div> + <div class="changes">${h.fancy_file_stats(stat)}</div> + </div> + %endfor + </div> + </div> + </div> + + ## diff block + <%namespace name="diff_block" file="/changeset/diff_block.html"/> + %for fid, change, f, stat in c.files: + ${diff_block.diff_block_simple([c.changes[fid]])} + %endfor + + <script type="text/javascript"> + + YUE.onDOMReady(function(){ + + YUE.on(YUQ('.diff-menu-activate'),'click',function(e){ + var act = e.currentTarget.nextElementSibling; + + if(YUD.hasClass(act,'active')){ + YUD.removeClass(act,'active'); + YUD.setStyle(act,'display','none'); + }else{ + YUD.addClass(act,'active'); + YUD.setStyle(act,'display',''); + } + }); + }) + </script> + </div> +</%def>
--- a/rhodecode/templates/email_templates/changeset_comment.html Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/templates/email_templates/changeset_comment.html Mon Jun 18 00:35:13 2012 +0200 @@ -4,3 +4,9 @@ <h4>${subject}</h4> ${body} + +% if status_change is not None: +<div> + New status -> ${status_change} +</div> +% endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/pullrequests/pullrequest.html Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,192 @@ +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${c.repo_name} ${_('New pull request')} +</%def> + +<%def name="breadcrumbs_links()"> + ${h.link_to(u'Home',h.url('/'))} + » + ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))} + » + ${_('New pull request')} +</%def> + +<%def name="main()"> + +<div class="box"> + <!-- box / title --> + <div class="title"> + ${self.breadcrumbs()} + </div> + ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')} + <div style="float:left;padding:0px 30px 30px 30px"> + <div style="padding:0px 5px 5px 5px"> + <span> + <a id="refresh" href="#"> + <img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/> + ${_('refresh overview')} + </a> + </span> + </div> + ##ORG + <div style="float:left"> + <div class="fork_user"> + <div class="gravatar"> + <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/> + </div> + <span style="font-size: 20px"> + ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')} + </span> + <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div> + </div> + <div style="clear:both;padding-top: 10px"></div> + </div> + <div style="float:left;font-size:24px;padding:0px 20px"> + <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/> + </div> + + ##OTHER, most Probably the PARENT OF THIS FORK + <div style="float:left"> + <div class="fork_user"> + <div class="gravatar"> + <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/> + </div> + <span style="font-size: 20px"> + ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref','',c.other_refs,class_='refs')} + </span> + <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div> + </div> + <div style="clear:both;padding-top: 10px"></div> + </div> + <div style="clear:both;padding-top: 10px"></div> + ## overview pulled by ajax + <div style="float:left" id="pull_request_overview"></div> + <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none"> + <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a> + </div> + </div> + <div style="float:left; border-left:1px dashed #eee"> + <h4>${_('Pull request reviewers')}</h4> + <div id="reviewers" style="padding:0px 0px 0px 15px"> + ##TODO: make this nicer :) + <table class="table noborder"> + <tr> + <td> + <div> + <div style="float:left"> + <div class="text" style="padding: 0px 0px 6px;">${_('Chosen reviewers')}</div> + ${h.select('review_members',[x[0] for x in c.review_members],c.review_members,multiple=True,size=8,style="min-width:210px")} + <div id="remove_all_elements" style="cursor:pointer;text-align:center"> + ${_('Remove all elements')} + <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/> + </div> + </div> + <div style="float:left;width:20px;padding-top:50px"> + <img alt="add" id="add_element" + style="padding:2px;cursor:pointer" + src="${h.url('/images/icons/arrow_left.png')}"/> + <br /> + <img alt="remove" id="remove_element" + style="padding:2px;cursor:pointer" + src="${h.url('/images/icons/arrow_right.png')}"/> + </div> + <div style="float:left"> + <div class="text" style="padding: 0px 0px 6px;">${_('Available reviewers')}</div> + ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")} + <div id="add_all_elements" style="cursor:pointer;text-align:center"> + <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/> + ${_('Add all elements')} + </div> + </div> + </div> + </td> + </tr> + </table> + </div> + </div> + <h3>${_('Create new pull request')}</h3> + + <div class="form"> + <!-- fields --> + + <div class="fields"> + + <div class="field"> + <div class="label"> + <label for="pullrequest_title">${_('Title')}:</label> + </div> + <div class="input"> + ${h.text('pullrequest_title',size=30)} + </div> + </div> + + <div class="field"> + <div class="label label-textarea"> + <label for="pullrequest_desc">${_('description')}:</label> + </div> + <div class="textarea text-area editor"> + ${h.textarea('pullrequest_desc',size=30)} + </div> + </div> + + <div class="buttons"> + ${h.submit('save',_('Send pull request'),class_="ui-button")} + ${h.reset('reset',_('Reset'),class_="ui-button")} + </div> + </div> + </div> + ${h.end_form()} + +</div> + +<script type="text/javascript"> + MultiSelectWidget('review_members','available_members','pull_request_form'); + + var loadPreview = function(){ + YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none'); + var url = "${h.url('compare_url', + repo_name='org_repo', + org_ref_type='branch', org_ref='org_ref', + other_ref_type='branch', other_ref='other_ref', + repo='other_repo', + as_form=True)}"; + + var select_refs = YUQ('#pull_request_form select.refs') + + for(var i=0;i<select_refs.length;i++){ + var select_ref = select_refs[i]; + var select_ref_data = select_ref.value.split(':'); + var key = null; + var val = null; + if(select_ref_data.length>1){ + key = select_ref.name+"_type"; + val = select_ref_data[0]; + url = url.replace(key,val); + + key = select_ref.name; + val = select_ref_data[1]; + url = url.replace(key,val); + + }else{ + key = select_ref.name; + val = select_ref.value; + url = url.replace(key,val); + } + } + + ypjax(url,'pull_request_overview', function(data){ + YUD.get('pull_request_overview_url').href = url; + YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display',''); + }) + } + YUE.on('refresh','click',function(e){ + loadPreview() + }) + + //lazy load overview after 0.5s + setTimeout(loadPreview, 500) + +</script> + +</%def>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/pullrequests/pullrequest_show.html Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,83 @@ +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id} +</%def> + +<%def name="breadcrumbs_links()"> + ${h.link_to(u'Home',h.url('/'))} + » + ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))} + » + ${_('Pull request #%s') % c.pull_request.pull_request_id} +</%def> + +<%def name="main()"> + +<div class="box"> + <!-- box / title --> + <div class="title"> + ${self.breadcrumbs()} + </div> + + <h3>${_('Title')}: ${c.pull_request.title}</h3> + <div class="changeset-status-container" style="float:left;padding:0px 20px 20px 20px"> + %if c.current_changeset_status: + <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div> + <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div> + %endif + </div> + <div style="padding:4px"> + <div>${h.fmt_date(c.pull_request.created_on)}</div> + </div> + + ##DIFF + + <div class="table"> + <div id="body" class="diffblock"> + <div style="white-space:pre-wrap;padding:5px">${h.literal(c.pull_request.description)}</div> + </div> + <div id="changeset_compare_view_content"> + ##CS + <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div> + <%include file="/compare/compare_cs.html" /> + + ## FILES + <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div> + <div class="cs_files"> + %for fid, change, f, stat in c.files: + <div class="cs_${change}"> + <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div> + <div class="changes">${h.fancy_file_stats(stat)}</div> + </div> + %endfor + </div> + </div> + </div> + <script> + var _USERS_AC_DATA = ${c.users_array|n}; + var _GROUPS_AC_DATA = ${c.users_groups_array|n}; + </script> + + ## diff block + <%namespace name="diff_block" file="/changeset/diff_block.html"/> + %for fid, change, f, stat in c.files: + ${diff_block.diff_block_simple([c.changes[fid]])} + %endfor + + ## template for inline comment form + <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> + ##${comment.comment_inline_form(c.changeset)} + + ## render comments main comments form and it status + ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), + c.current_changeset_status)} + +</div> + +<script type="text/javascript"> + + +</script> + +</%def>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/pullrequests/pullrequest_show_all.html Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,31 @@ +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${c.repo_name} ${_('All pull requests')} +</%def> + +<%def name="breadcrumbs_links()"> + ${h.link_to(u'Home',h.url('/'))} + » + ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))} + » + ${_('All pull requests')} +</%def> + +<%def name="main()"> + +<div class="box"> + <!-- box / title --> + <div class="title"> + ${self.breadcrumbs()} + </div> + + %for pr in c.pull_requests: + <a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">#${pr.pull_request_id}</a> + %endfor + +</div> + +<script type="text/javascript"></script> + +</%def>
--- a/rhodecode/tests/__init__.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/tests/__init__.py Mon Jun 18 00:35:13 2012 +0200 @@ -27,7 +27,8 @@ from rhodecode import is_windows from rhodecode.model.meta import Session from rhodecode.model.db import User - +from rhodecode.tests.nose_parametrized import parameterized + import pylons.test @@ -38,9 +39,9 @@ log = logging.getLogger(__name__) __all__ = [ - 'environ', 'url', 'get_new_dir', 'TestController', 'TESTS_TMP_PATH', - 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', - 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_REGULAR_LOGIN', + 'parameterized', 'environ', 'url', 'get_new_dir', 'TestController', + 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', + 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS', 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN', 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO', 'TEST_HG_REPO_CLONE', @@ -54,6 +55,7 @@ ##RUNNING DESIRED TESTS # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account # nosetests --pdb --pdb-failures +# nosetests --with-coverage --cover-package=rhodecode.model.validators rhodecode.tests.test_validators environ = {} #SOME GLOBALS FOR TESTS
--- a/rhodecode/tests/functional/test_admin_settings.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/tests/functional/test_admin_settings.py Mon Jun 18 00:35:13 2012 +0200 @@ -3,6 +3,8 @@ from rhodecode.lib.auth import get_crypt_password, check_password from rhodecode.model.db import User, RhodeCodeSetting from rhodecode.tests import * +from rhodecode.lib import helpers as h + class TestAdminSettingsController(TestController): @@ -47,7 +49,6 @@ response = self.app.get(url('formatted_admin_edit_setting', setting_id=1, format='xml')) - def test_ga_code_active(self): self.log_user() old_title = 'RhodeCode' @@ -92,7 +93,6 @@ self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code not in response.body) - def test_title_change(self): self.log_user() old_title = 'RhodeCode' @@ -117,7 +117,6 @@ self.assertTrue("""<h1><a href="/">%s</a></h1>""" % new_title in response.body) - def test_my_account(self): self.log_user() response = self.app.get(url('admin_settings_my_account')) @@ -132,12 +131,11 @@ new_lastname = 'NewLastname' new_password = 'test123' - response = self.app.post(url('admin_settings_my_account_update'), params=dict(_method='put', username='test_admin', new_password=new_password, - password_confirmation = new_password, + password_confirmation=new_password, password='', name=new_name, lastname=new_lastname, @@ -146,7 +144,7 @@ assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change' user = self.Session.query(User).filter(User.username == 'test_admin').one() - assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email) + assert user.email == new_email, 'incorrect user email after update got %s vs %s' % (user.email, new_email) assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name) assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname) assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password) @@ -161,7 +159,7 @@ _method='put', username='test_admin', new_password=old_password, - password_confirmation = old_password, + password_confirmation=old_password, password='', name=old_name, lastname=old_lastname, @@ -172,41 +170,46 @@ 'Your account was updated successfully') user = self.Session.query(User).filter(User.username == 'test_admin').one() - assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email) + assert user.email == old_email, 'incorrect user email after update got %s vs %s' % (user.email, old_email) - assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email) + assert user.email == old_email, 'incorrect user email after update got %s vs %s' % (user.email, old_email) assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name) assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname) - assert check_password(old_password, user.password) is True , 'password updated field mismatch %s vs %s' % (user.password, old_password) - + assert check_password(old_password, user.password) is True, 'password updated field mismatch %s vs %s' % (user.password, old_password) def test_my_account_update_err_email_exists(self): self.log_user() - new_email = 'test_regular@mail.com'#already exisitn email + new_email = 'test_regular@mail.com' # already exisitn email response = self.app.post(url('admin_settings_my_account_update'), params=dict( _method='put', username='test_admin', new_password='test12', - password_confirmation = 'test122', + password_confirmation='test122', name='NewName', lastname='NewLastname', email=new_email,)) assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email' - def test_my_account_update_err(self): self.log_user('test_regular2', 'test12') new_email = 'newmail.pl' - response = self.app.post(url('admin_settings_my_account_update'), params=dict( - _method='put', - username='test_admin', - new_password='test12', - password_confirmation = 'test122', - name='NewName', - lastname='NewLastname', - email=new_email,)) - assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email' - assert 'This username already exists' in response.body, 'Missing error message about existing user' + response = self.app.post(url('admin_settings_my_account_update'), + params=dict( + _method='put', + username='test_admin', + new_password='test12', + password_confirmation='test122', + name='NewName', + lastname='NewLastname', + email=new_email,) + ) + + response.mustcontain('An email address must contain a single @') + from rhodecode.model import validators + msg = validators.ValidUsername(edit=False, + old_data={})._messages['username_exists'] + msg = h.html_escape(msg % {'username': 'test_admin'}) + response.mustcontain(u"%s" % msg)
--- a/rhodecode/tests/functional/test_admin_users.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/tests/functional/test_admin_users.py Mon Jun 18 00:35:13 2012 +0200 @@ -1,8 +1,12 @@ +from sqlalchemy.orm.exc import NoResultFound + from rhodecode.tests import * from rhodecode.model.db import User, Permission from rhodecode.lib.auth import check_password -from sqlalchemy.orm.exc import NoResultFound from rhodecode.model.user import UserModel +from rhodecode.model import validators +from rhodecode.lib import helpers as h + class TestAdminUsersController(TestController): @@ -24,26 +28,25 @@ email = 'mail@mail.com' response = self.app.post(url('users'), - {'username':username, - 'password':password, - 'password_confirmation':password_confirmation, - 'name':name, - 'active':True, - 'lastname':lastname, - 'email':email}) + {'username': username, + 'password': password, + 'password_confirmation': password_confirmation, + 'name': name, + 'active': True, + 'lastname': lastname, + 'email': email}) + self.checkSessionFlash(response, '''created user %s''' % (username)) - self.assertTrue('''created user %s''' % (username) in - response.session['flash'][0]) new_user = self.Session.query(User).\ filter(User.username == username).one() - self.assertEqual(new_user.username,username) - self.assertEqual(check_password(password, new_user.password),True) - self.assertEqual(new_user.name,name) - self.assertEqual(new_user.lastname,lastname) - self.assertEqual(new_user.email,email) + self.assertEqual(new_user.username, username) + self.assertEqual(check_password(password, new_user.password), True) + self.assertEqual(new_user.name, name) + self.assertEqual(new_user.lastname, lastname) + self.assertEqual(new_user.email, email) response.follow() response = response.follow() @@ -57,16 +60,18 @@ lastname = 'lastname' email = 'errmail.com' - response = self.app.post(url('users'), {'username':username, - 'password':password, - 'name':name, - 'active':False, - 'lastname':lastname, - 'email':email}) + response = self.app.post(url('users'), {'username': username, + 'password': password, + 'name': name, + 'active': False, + 'lastname': lastname, + 'email': email}) - self.assertTrue("""<span class="error-message">Invalid username</span>""" in response.body) - self.assertTrue("""<span class="error-message">Please enter a value</span>""" in response.body) - self.assertTrue("""<span class="error-message">An email address must contain a single @</span>""" in response.body) + msg = validators.ValidUsername(False, {})._messages['system_invalid_username'] + msg = h.html_escape(msg % {'username': 'new_user'}) + response.mustcontain("""<span class="error-message">%s</span>""" % msg) + response.mustcontain("""<span class="error-message">Please enter a value</span>""") + response.mustcontain("""<span class="error-message">An email address must contain a single @</span>""") def get_user(): self.Session.query(User).filter(User.username == username).one() @@ -94,13 +99,13 @@ lastname = 'lastname' email = 'todeletemail@mail.com' - response = self.app.post(url('users'), {'username':username, - 'password':password, - 'password_confirmation':password, - 'name':name, - 'active':True, - 'lastname':lastname, - 'email':email}) + response = self.app.post(url('users'), {'username': username, + 'password': password, + 'password_confirmation': password, + 'name': name, + 'active': True, + 'lastname': lastname, + 'email': email}) response = response.follow() @@ -111,7 +116,6 @@ self.assertTrue("""successfully deleted user""" in response.session['flash'][0]) - def test_delete_browser_fakeout(self): response = self.app.post(url('user', id=1), params=dict(_method='delete')) @@ -127,7 +131,6 @@ user = User.get_by_username(TEST_USER_ADMIN_LOGIN) response = self.app.get(url('edit_user', id=user.user_id)) - def test_add_perm_create_repo(self): self.log_user() perm_none = Permission.get_by_key('hg.create.none') @@ -135,7 +138,6 @@ user = User.get_by_username(TEST_USER_REGULAR_LOGIN) - #User should have None permission on creation repository self.assertEqual(UserModel().has_perm(user, perm_none), False) self.assertEqual(UserModel().has_perm(user, perm_create), False) @@ -159,7 +161,6 @@ user = User.get_by_username(TEST_USER_REGULAR2_LOGIN) - #User should have None permission on creation repository self.assertEqual(UserModel().has_perm(user, perm_none), False) self.assertEqual(UserModel().has_perm(user, perm_create), False)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/tests/functional/test_compare.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,52 @@ +from rhodecode.tests import * + + +class TestCompareController(TestController): + + def test_index_tag(self): + self.log_user() + tag1='0.1.3' + tag2='0.1.2' + response = self.app.get(url(controller='compare', action='index', + repo_name=HG_REPO, + org_ref_type="tag", + org_ref=tag1, + other_ref_type="tag", + other_ref=tag2, + )) + response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2)) + ## outgoing changesets between tags + response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO) + response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO) + response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO) + response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO) + response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO) + response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO) + response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO) + + ## files diff + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2)) + + def test_index_branch(self): + self.log_user() + response = self.app.get(url(controller='compare', action='index', + repo_name=HG_REPO, + org_ref_type="branch", + org_ref='default', + other_ref_type="branch", + other_ref='default', + )) + + response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO)) + # branch are equal + response.mustcontain('<tr><td>No changesets</td></tr>')
--- a/rhodecode/tests/functional/test_login.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/tests/functional/test_login.py Mon Jun 18 00:35:13 2012 +0200 @@ -4,6 +4,8 @@ from rhodecode.lib.utils2 import generate_api_key from rhodecode.lib.auth import check_password from rhodecode.model.meta import Session +from rhodecode.lib import helpers as h +from rhodecode.model import validators class TestLoginController(TestController): @@ -22,21 +24,21 @@ def test_login_admin_ok(self): response = self.app.post(url(controller='login', action='index'), - {'username':'test_admin', - 'password':'test12'}) + {'username': 'test_admin', + 'password': 'test12'}) self.assertEqual(response.status, '302 Found') - self.assertEqual(response.session['rhodecode_user'].get('username') , + self.assertEqual(response.session['rhodecode_user'].get('username'), 'test_admin') response = response.follow() self.assertTrue('%s repository' % HG_REPO in response.body) def test_login_regular_ok(self): response = self.app.post(url(controller='login', action='index'), - {'username':'test_regular', - 'password':'test12'}) + {'username': 'test_regular', + 'password': 'test12'}) self.assertEqual(response.status, '302 Found') - self.assertEqual(response.session['rhodecode_user'].get('username') , + self.assertEqual(response.session['rhodecode_user'].get('username'), 'test_regular') response = response.follow() self.assertTrue('%s repository' % HG_REPO in response.body) @@ -46,8 +48,8 @@ test_came_from = '/_admin/users' response = self.app.post(url(controller='login', action='index', came_from=test_came_from), - {'username':'test_admin', - 'password':'test12'}) + {'username': 'test_admin', + 'password': 'test12'}) self.assertEqual(response.status, '302 Found') response = response.follow() @@ -56,17 +58,16 @@ def test_login_short_password(self): response = self.app.post(url(controller='login', action='index'), - {'username':'test_admin', - 'password':'as'}) + {'username': 'test_admin', + 'password': 'as'}) self.assertEqual(response.status, '200 OK') self.assertTrue('Enter 3 characters or more' in response.body) def test_login_wrong_username_password(self): response = self.app.post(url(controller='login', action='index'), - {'username':'error', - 'password':'test12'}) - self.assertEqual(response.status , '200 OK') + {'username': 'error', + 'password': 'test12'}) self.assertTrue('invalid user name' in response.body) self.assertTrue('invalid password' in response.body) @@ -79,62 +80,63 @@ self.assertTrue('Sign Up to RhodeCode' in response.body) def test_register_err_same_username(self): + uname = 'test_admin' response = self.app.post(url(controller='login', action='register'), - {'username':'test_admin', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'goodmail@domain.com', - 'name':'test', - 'lastname':'test'}) + {'username': uname, + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'goodmail@domain.com', + 'name': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') - self.assertTrue('This username already exists' in response.body) + msg = validators.ValidUsername()._messages['username_exists'] + msg = h.html_escape(msg % {'username': uname}) + response.mustcontain(msg) def test_register_err_same_email(self): response = self.app.post(url(controller='login', action='register'), - {'username':'test_admin_0', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'test_admin@mail.com', - 'name':'test', - 'lastname':'test'}) + {'username': 'test_admin_0', + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'test_admin@mail.com', + 'name': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') - response.mustcontain('This e-mail address is already taken') + msg = validators.UniqSystemEmail()()._messages['email_taken'] + response.mustcontain(msg) def test_register_err_same_email_case_sensitive(self): response = self.app.post(url(controller='login', action='register'), - {'username':'test_admin_1', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'TesT_Admin@mail.COM', - 'name':'test', - 'lastname':'test'}) - self.assertEqual(response.status , '200 OK') - response.mustcontain('This e-mail address is already taken') + {'username': 'test_admin_1', + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'TesT_Admin@mail.COM', + 'name': 'test', + 'lastname': 'test'}) + msg = validators.UniqSystemEmail()()._messages['email_taken'] + response.mustcontain(msg) def test_register_err_wrong_data(self): response = self.app.post(url(controller='login', action='register'), - {'username':'xs', - 'password':'test', - 'password_confirmation':'test', - 'email':'goodmailm', - 'name':'test', - 'lastname':'test'}) - self.assertEqual(response.status , '200 OK') + {'username': 'xs', + 'password': 'test', + 'password_confirmation': 'test', + 'email': 'goodmailm', + 'name': 'test', + 'lastname': 'test'}) + self.assertEqual(response.status, '200 OK') response.mustcontain('An email address must contain a single @') response.mustcontain('Enter a value 6 characters long or more') def test_register_err_username(self): response = self.app.post(url(controller='login', action='register'), - {'username':'error user', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'goodmailm', - 'name':'test', - 'lastname':'test'}) + {'username': 'error user', + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'goodmailm', + 'name': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') response.mustcontain('An email address must contain a single @') response.mustcontain('Username may only contain ' 'alphanumeric characters underscores, ' @@ -142,41 +144,42 @@ 'alphanumeric character') def test_register_err_case_sensitive(self): + usr = 'Test_Admin' response = self.app.post(url(controller='login', action='register'), - {'username':'Test_Admin', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'goodmailm', - 'name':'test', - 'lastname':'test'}) + {'username': usr, + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'goodmailm', + 'name': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') - self.assertTrue('An email address must contain a single @' in response.body) - self.assertTrue('This username already exists' in response.body) + response.mustcontain('An email address must contain a single @') + msg = validators.ValidUsername()._messages['username_exists'] + msg = h.html_escape(msg % {'username': usr}) + response.mustcontain(msg) def test_register_special_chars(self): response = self.app.post(url(controller='login', action='register'), - {'username':'xxxaxn', - 'password':'ąćźżąśśśś', - 'password_confirmation':'ąćźżąśśśś', - 'email':'goodmailm@test.plx', - 'name':'test', - 'lastname':'test'}) + {'username': 'xxxaxn', + 'password': 'ąćźżąśśśś', + 'password_confirmation': 'ąćźżąśśśś', + 'email': 'goodmailm@test.plx', + 'name': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') - self.assertTrue('Invalid characters in password' in response.body) + msg = validators.ValidPassword()._messages['invalid_password'] + response.mustcontain(msg) def test_register_password_mismatch(self): response = self.app.post(url(controller='login', action='register'), - {'username':'xs', - 'password':'123qwe', - 'password_confirmation':'qwe123', - 'email':'goodmailm@test.plxa', - 'name':'test', - 'lastname':'test'}) - - self.assertEqual(response.status, '200 OK') - response.mustcontain('Passwords do not match') + {'username': 'xs', + 'password': '123qwe', + 'password_confirmation': 'qwe123', + 'email': 'goodmailm@test.plxa', + 'name': 'test', + 'lastname': 'test'}) + msg = validators.ValidPasswordsMatch()._messages['password_mismatch'] + response.mustcontain(msg) def test_register_ok(self): username = 'test_regular4' @@ -186,13 +189,13 @@ lastname = 'testlastname' response = self.app.post(url(controller='login', action='register'), - {'username':username, - 'password':password, - 'password_confirmation':password, - 'email':email, - 'name':name, - 'lastname':lastname, - 'admin':True}) # This should be overriden + {'username': username, + 'password': password, + 'password_confirmation': password, + 'email': email, + 'name': name, + 'lastname': lastname, + 'admin': True}) # This should be overriden self.assertEqual(response.status, '302 Found') self.checkSessionFlash(response, 'You have successfully registered into rhodecode') @@ -206,12 +209,15 @@ self.assertEqual(ret.admin, False) def test_forgot_password_wrong_mail(self): + bad_email = 'marcin@wrongmail.org' response = self.app.post( url(controller='login', action='password_reset'), - {'email': 'marcin@wrongmail.org',} + {'email': bad_email, } ) - response.mustcontain("This e-mail address doesn't exist") + msg = validators.ValidSystemEmail()._messages['non_existing_email'] + msg = h.html_escape(msg % {'email': bad_email}) + response.mustcontain() def test_forgot_password(self): response = self.app.get(url(controller='login', @@ -236,7 +242,7 @@ response = self.app.post(url(controller='login', action='password_reset'), - {'email':email, }) + {'email': email, }) self.checkSessionFlash(response, 'Your password reset link was sent')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/tests/functional/test_pullrequests.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,9 @@ +from rhodecode.tests import * + + +class TestPullrequestsController(TestController): + + def test_index(self): + self.log_user() + response = self.app.get(url(controller='pullrequests', action='index', + repo_name=HG_REPO))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/tests/nose_parametrized.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,238 @@ +import re +import new +import inspect +import logging +import logging.handlers +from functools import wraps + +from nose.tools import nottest +from unittest import TestCase + + +def _terrible_magic_get_defining_classes(): + """ Returns the set of parent classes of the class currently being defined. + Will likely only work if called from the ``parameterized`` decorator. + This function is entirely @brandon_rhodes's fault, as he suggested + the implementation: http://stackoverflow.com/a/8793684/71522 + """ + stack = inspect.stack() + if len(stack) <= 4: + return [] + frame = stack[3] + code_context = frame[4][0].strip() + if not code_context.startswith("class "): + return [] + _, parents = code_context.split("(", 1) + parents, _ = parents.rsplit(")", 1) + return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals) + + +def parameterized(input): + """ Parameterize a test case: + >>> add1_tests = [(1, 2), (2, 3)] + >>> class TestFoo(object): + ... @parameterized(add1_tests) + ... def test_add1(self, input, expected): + ... assert_equal(add1(input), expected) + >>> @parameterized(add1_tests) + ... def test_add1(input, expected): + ... assert_equal(add1(input), expected) + >>> + """ + + if not hasattr(input, "__iter__"): + raise ValueError("expected iterable input; got %r" % (input,)) + + def parameterized_helper(f): + attached_instance_method = [False] + + parent_classes = _terrible_magic_get_defining_classes() + if any(issubclass(cls, TestCase) for cls in parent_classes): + raise Exception("Warning: '@parameterized' tests won't work " + "inside subclasses of 'TestCase' - use " + "'@parameterized.expand' instead") + + @wraps(f) + def parameterized_helper_method(self=None): + if self is not None and not attached_instance_method[0]: + # confusingly, we need to create a named instance method and + # attach that to the class... + cls = self.__class__ + im_f = new.instancemethod(f, None, cls) + setattr(cls, f.__name__, im_f) + attached_instance_method[0] = True + for args in input: + if isinstance(args, basestring): + args = [args] + # ... then pull that named instance method off, turning it into + # a bound method ... + if self is not None: + args = [getattr(self, f.__name__)] + list(args) + else: + args = [f] + list(args) + # ... then yield that as a tuple. If those steps aren't + # followed precicely, Nose gets upset and doesn't run the test + # or doesn't run setup methods. + yield tuple(args) + + f.__name__ = "_helper_for_%s" % (f.__name__,) + parameterized_helper_method.parameterized_input = input + parameterized_helper_method.parameterized_func = f + return parameterized_helper_method + + return parameterized_helper + + +def to_safe_name(s): + return re.sub("[^a-zA-Z0-9_]", "", s) + + +def parameterized_expand_helper(func_name, func, args): + def parameterized_expand_helper_helper(self=()): + if self != (): + self = (self,) + return func(*(self + args)) + parameterized_expand_helper_helper.__name__ = func_name + return parameterized_expand_helper_helper + + +def parameterized_expand(input): + """ A "brute force" method of parameterizing test cases. Creates new test + cases and injects them into the namespace that the wrapped function + is being defined in. Useful for parameterizing tests in subclasses + of 'UnitTest', where Nose test generators don't work. + + >>> @parameterized.expand([("foo", 1, 2)]) + ... def test_add1(name, input, expected): + ... actual = add1(input) + ... assert_equal(actual, expected) + ... + >>> locals() + ... 'test_add1_foo_0': <function ...> ... + >>> + """ + + def parameterized_expand_wrapper(f): + stack = inspect.stack() + frame = stack[1] + frame_locals = frame[0].f_locals + + base_name = f.__name__ + for num, args in enumerate(input): + name_suffix = "_%s" % (num,) + if len(args) > 0 and isinstance(args[0], basestring): + name_suffix += "_" + to_safe_name(args[0]) + name = base_name + name_suffix + new_func = parameterized_expand_helper(name, f, args) + frame_locals[name] = new_func + return nottest(f) + return parameterized_expand_wrapper + +parameterized.expand = parameterized_expand + + +def assert_contains(haystack, needle): + if needle not in haystack: + raise AssertionError("%r not in %r" % (needle, haystack)) + + +def assert_not_contains(haystack, needle): + if needle in haystack: + raise AssertionError("%r in %r" % (needle, haystack)) + + +def imported_from_test(): + """ Returns true if it looks like this module is being imported by unittest + or nose. """ + import re + import inspect + nose_re = re.compile(r"\bnose\b") + unittest_re = re.compile(r"\bunittest2?\b") + for frame in inspect.stack(): + file = frame[1] + if nose_re.search(file) or unittest_re.search(file): + return True + return False + + +def assert_raises(func, exc_type, str_contains=None, repr_contains=None): + try: + func() + except exc_type as e: + if str_contains is not None and str_contains not in str(e): + raise AssertionError("%s raised, but %r does not contain %r" + % (exc_type, str(e), str_contains)) + if repr_contains is not None and repr_contains not in repr(e): + raise AssertionError("%s raised, but %r does not contain %r" + % (exc_type, repr(e), repr_contains)) + return e + else: + raise AssertionError("%s not raised" % (exc_type,)) + + +log_handler = None + + +def setup_logging(): + """ Configures a log handler which will capure log messages during a test. + The ``logged_messages`` and ``assert_no_errors_logged`` functions can be + used to make assertions about these logged messages. + + For example:: + + from ensi_common.testing import ( + setup_logging, teardown_logging, assert_no_errors_logged, + assert_logged, + ) + + class TestWidget(object): + def setup(self): + setup_logging() + + def teardown(self): + assert_no_errors_logged() + teardown_logging() + + def test_that_will_fail(self): + log.warning("this warning message will trigger a failure") + + def test_that_will_pass(self): + log.info("but info messages are ok") + assert_logged("info messages are ok") + """ + + global log_handler + if log_handler is not None: + logging.getLogger().removeHandler(log_handler) + log_handler = logging.handlers.BufferingHandler(1000) + formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s") + log_handler.setFormatter(formatter) + logging.getLogger().addHandler(log_handler) + + +def teardown_logging(): + global log_handler + if log_handler is not None: + logging.getLogger().removeHandler(log_handler) + log_handler = None + + +def logged_messages(): + assert log_handler, "setup_logging not called" + return [(log_handler.format(record), record) for record in log_handler.buffer] + + +def assert_no_errors_logged(): + for _, record in logged_messages(): + if record.levelno >= logging.WARNING: + # Assume that the nose log capture plugin is being used, so it will + # show the exception. + raise AssertionError("an unexpected error was logged") + + +def assert_logged(expected_msg_contents): + for msg, _ in logged_messages(): + if expected_msg_contents in msg: + return + raise AssertionError("no logged message contains %r" + % (expected_msg_contents,))
--- a/rhodecode/tests/test_models.py Mon Jun 18 00:33:19 2012 +0200 +++ b/rhodecode/tests/test_models.py Mon Jun 18 00:35:13 2012 +0200 @@ -6,8 +6,8 @@ from rhodecode.model.repo import RepoModel from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \ UsersGroup, UsersGroupMember, Permission, UsersGroupRepoGroupToPerm,\ - Repository -from sqlalchemy.exc import IntegrityError + Repository, UserEmailMap +from sqlalchemy.exc import IntegrityError, DatabaseError from rhodecode.model.user import UserModel from rhodecode.model.meta import Session @@ -182,7 +182,8 @@ super(TestUser, self).__init__(methodName=methodName) def test_create_and_remove(self): - usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe', + usr = UserModel().create_or_update(username=u'test_user', + password=u'qweqwe', email=u'u232@rhodecode.org', name=u'u1', lastname=u'u1') Session.commit() @@ -202,6 +203,50 @@ self.assertEqual(UsersGroupMember.query().all(), []) + def test_additonal_email_as_main(self): + usr = UserModel().create_or_update(username=u'test_user', + password=u'qweqwe', + email=u'main_email@rhodecode.org', + name=u'u1', lastname=u'u1') + Session.commit() + + def do(): + m = UserEmailMap() + m.email = u'main_email@rhodecode.org' + m.user = usr + Session.add(m) + Session.commit() + self.assertRaises(AttributeError, do) + + UserModel().delete(usr.user_id) + Session.commit() + + def test_extra_email_map(self): + usr = UserModel().create_or_update(username=u'test_user', + password=u'qweqwe', + email=u'main_email@rhodecode.org', + name=u'u1', lastname=u'u1') + Session.commit() + + m = UserEmailMap() + m.email = u'main_email2@rhodecode.org' + m.user = usr + Session.add(m) + Session.commit() + + u = User.get_by_email(email='main_email@rhodecode.org') + self.assertEqual(usr.user_id, u.user_id) + self.assertEqual(usr.username, u.username) + + u = User.get_by_email(email='main_email2@rhodecode.org') + self.assertEqual(usr.user_id, u.user_id) + self.assertEqual(usr.username, u.username) + u = User.get_by_email(email='main_email3@rhodecode.org') + self.assertEqual(None, u) + + UserModel().delete(usr.user_id) + Session.commit() + class TestNotifications(unittest.TestCase):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/tests/test_validators.py Mon Jun 18 00:35:13 2012 +0200 @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- +import unittest +import formencode + +from rhodecode.tests import * + +from rhodecode.model import validators as v +from rhodecode.model.users_group import UsersGroupModel + +from rhodecode.model.meta import Session +from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.config.routing import ADMIN_PREFIX + + +class TestReposGroups(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_Message_extractor(self): + validator = v.ValidUsername() + self.assertRaises(formencode.Invalid, validator.to_python, 'default') + + class StateObj(object): + pass + + self.assertRaises(formencode.Invalid, + validator.to_python, 'default', StateObj) + + def test_ValidUsername(self): + validator = v.ValidUsername() + + self.assertRaises(formencode.Invalid, validator.to_python, 'default') + self.assertRaises(formencode.Invalid, validator.to_python, 'new_user') + self.assertRaises(formencode.Invalid, validator.to_python, '.,') + self.assertRaises(formencode.Invalid, validator.to_python, + TEST_USER_ADMIN_LOGIN) + self.assertEqual('test', validator.to_python('test')) + + validator = v.ValidUsername(edit=True, old_data={'user_id': 1}) + + def test_ValidRepoUser(self): + validator = v.ValidRepoUser() + self.assertRaises(formencode.Invalid, validator.to_python, 'nouser') + self.assertEqual(TEST_USER_ADMIN_LOGIN, + validator.to_python(TEST_USER_ADMIN_LOGIN)) + + def test_ValidUsersGroup(self): + validator = v.ValidUsersGroup() + self.assertRaises(formencode.Invalid, validator.to_python, 'default') + self.assertRaises(formencode.Invalid, validator.to_python, '.,') + + gr = UsersGroupModel().create('test') + gr2 = UsersGroupModel().create('tes2') + Session.commit() + self.assertRaises(formencode.Invalid, validator.to_python, 'test') + assert gr.users_group_id != None + validator = v.ValidUsersGroup(edit=True, + old_data={'users_group_id': + gr2.users_group_id}) + + self.assertRaises(formencode.Invalid, validator.to_python, 'test') + self.assertRaises(formencode.Invalid, validator.to_python, 'TesT') + self.assertRaises(formencode.Invalid, validator.to_python, 'TEST') + UsersGroupModel().delete(gr) + UsersGroupModel().delete(gr2) + Session.commit() + + def test_ValidReposGroup(self): + validator = v.ValidReposGroup() + model = ReposGroupModel() + self.assertRaises(formencode.Invalid, validator.to_python, + {'group_name': HG_REPO, }) + gr = model.create(group_name='test_gr', group_description='desc', + parent=None, + just_db=True) + self.assertRaises(formencode.Invalid, + validator.to_python, {'group_name': gr.group_name, }) + + validator = v.ValidReposGroup(edit=True, + old_data={'group_id': gr.group_id}) + self.assertRaises(formencode.Invalid, + validator.to_python, { + 'group_name': gr.group_name + 'n', + 'group_parent_id': gr.group_id + }) + model.delete(gr) + + def test_ValidPassword(self): + validator = v.ValidPassword() + self.assertEqual('lol', validator.to_python('lol')) + self.assertEqual(None, validator.to_python(None)) + self.assertRaises(formencode.Invalid, validator.to_python, 'ąćżź') + + def test_ValidPasswordsMatch(self): + validator = v.ValidPasswordsMatch() + self.assertRaises(formencode.Invalid, + validator.to_python, {'password': 'pass', + 'password_confirmation': 'pass2'}) + + self.assertRaises(formencode.Invalid, + validator.to_python, {'new_password': 'pass', + 'password_confirmation': 'pass2'}) + + self.assertEqual({'new_password': 'pass', + 'password_confirmation': 'pass'}, + validator.to_python({'new_password': 'pass', + 'password_confirmation': 'pass'})) + + self.assertEqual({'password': 'pass', + 'password_confirmation': 'pass'}, + validator.to_python({'password': 'pass', + 'password_confirmation': 'pass'})) + + def test_ValidAuth(self): + validator = v.ValidAuth() + valid_creds = { + 'username': TEST_USER_REGULAR2_LOGIN, + 'password': TEST_USER_REGULAR2_PASS, + } + invalid_creds = { + 'username': 'err', + 'password': 'err', + } + self.assertEqual(valid_creds, validator.to_python(valid_creds)) + self.assertRaises(formencode.Invalid, + validator.to_python, invalid_creds) + + def test_ValidAuthToken(self): + validator = v.ValidAuthToken() + # this is untestable without a threadlocal +# self.assertRaises(formencode.Invalid, +# validator.to_python, 'BadToken') + validator + + def test_ValidRepoName(self): + validator = v.ValidRepoName() + + self.assertRaises(formencode.Invalid, + validator.to_python, {'repo_name': ''}) + + self.assertRaises(formencode.Invalid, + validator.to_python, {'repo_name': HG_REPO}) + + gr = ReposGroupModel().create(group_name='group_test', + group_description='desc', + parent=None,) + self.assertRaises(formencode.Invalid, + validator.to_python, {'repo_name': gr.group_name}) + + #TODO: write an error case for that ie. create a repo withinh a group +# self.assertRaises(formencode.Invalid, +# validator.to_python, {'repo_name': 'some', +# 'repo_group': gr.group_id}) + + def test_ValidForkName(self): + # this uses ValidRepoName validator + assert True + + @parameterized.expand([ + ('test', 'test'), ('lolz!', 'lolz'), (' aavv', 'aavv'), + ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'), + ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'), + ('/]re po', 're-po')]) + def test_SlugifyName(self, name, expected): + validator = v.SlugifyName() + self.assertEqual(expected, validator.to_python(name)) + + def test_ValidCloneUri(self): + assert False + + def test_ValidForkType(self): + validator = v.ValidForkType(old_data={'repo_type': 'hg'}) + self.assertEqual('hg', validator.to_python('hg')) + self.assertRaises(formencode.Invalid, validator.to_python, 'git') + + def test_ValidPerms(self): + assert False + + def test_ValidSettings(self): + validator = v.ValidSettings() + self.assertEqual({'pass': 'pass'}, + validator.to_python(value={'user': 'test', + 'pass': 'pass'})) + + self.assertEqual({'user2': 'test', 'pass': 'pass'}, + validator.to_python(value={'user2': 'test', + 'pass': 'pass'})) + + def test_ValidPath(self): + validator = v.ValidPath() + self.assertEqual(TESTS_TMP_PATH, + validator.to_python(TESTS_TMP_PATH)) + self.assertRaises(formencode.Invalid, validator.to_python, + '/no_such_dir') + + def test_UniqSystemEmail(self): + validator = v.UniqSystemEmail(old_data={}) + + self.assertEqual('mail@python.org', + validator.to_python('MaiL@Python.org')) + + email = TEST_USER_REGULAR2_EMAIL + self.assertRaises(formencode.Invalid, validator.to_python, email) + + def test_ValidSystemEmail(self): + validator = v.ValidSystemEmail() + email = TEST_USER_REGULAR2_EMAIL + + self.assertEqual(email, validator.to_python(email)) + self.assertRaises(formencode.Invalid, validator.to_python, 'err') + + def test_LdapLibValidator(self): + validator = v.LdapLibValidator() + self.assertRaises(v.LdapImportError, validator.to_python, 'err') + + def test_AttrLoginValidator(self): + validator = v.AttrLoginValidator() + self.assertRaises(formencode.Invalid, validator.to_python, 123)