diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py +++ b/rhodecode/controllers/admin/repos.py @@ -74,6 +74,8 @@ class ReposController(BaseController): c.repo_groups.extend([(x.group_id, parents_link(x)) for \ x in self.sa.query(Group).all()]) + c.repo_groups = sorted(c.repo_groups, + key=lambda t: t[1].split('»')[0]) c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) c.users_array = repo_model.get_users_js() c.users_groups_array = repo_model.get_users_groups_js() diff --git a/rhodecode/controllers/admin/repos_groups.py b/rhodecode/controllers/admin/repos_groups.py --- a/rhodecode/controllers/admin/repos_groups.py +++ b/rhodecode/controllers/admin/repos_groups.py @@ -1,11 +1,21 @@ import logging +import traceback +import formencode + +from formencode import htmlfill from operator import itemgetter from pylons import request, response, session, tmpl_context as c, url from pylons.controllers.util import abort, redirect +from pylons.i18n.translation import _ +from rhodecode.lib import helpers as h +from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \ + HasPermissionAnyDecorator from rhodecode.lib.base import BaseController, render from rhodecode.model.db import Group +from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.model.forms import ReposGroupForm log = logging.getLogger(__name__) @@ -16,18 +26,73 @@ class ReposGroupsController(BaseControll # file has a resource setup: # map.resource('repos_group', 'repos_groups') + def __load_defaults(self): + + c.repo_groups = [('', '')] + parents_link = lambda k: h.literal('»'.join( + map(lambda k: k.group_name, + k.parents + [k]) + ) + ) + + c.repo_groups.extend([(x.group_id, parents_link(x)) for \ + x in self.sa.query(Group).all()]) + + c.repo_groups = sorted(c.repo_groups, + key=lambda t: t[1].split('»')[0]) + c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) + + @LoginRequired() + def __before__(self): + super(ReposGroupsController, self).__before__() + + @HasPermissionAnyDecorator('hg.admin') def index(self, format='html'): """GET /repos_groups: All items in the collection""" # url('repos_groups') + sk = lambda g:g.parents[0].group_name if g.parents else g.group_name + c.groups = sorted(Group.query().all(), key=sk) + return render('admin/repos_groups/repos_groups_show.html') + + @HasPermissionAnyDecorator('hg.admin') def create(self): """POST /repos_groups: Create a new item""" # url('repos_groups') + self.__load_defaults() + repos_group_model = ReposGroupModel() + repos_group_form = ReposGroupForm(available_groups= + c.repo_groups_choices)() + try: + form_result = repos_group_form.to_python(dict(request.POST)) + repos_group_model.create(form_result) + h.flash(_('created repos group %s') \ + % form_result['repos_group_name'], category='success') + #TODO: in futureaction_logger(, '', '', '', self.sa) + except formencode.Invalid, errors: + return htmlfill.render( + render('admin/repos_groups/repos_groups_add.html'), + defaults=errors.value, + errors=errors.error_dict or {}, + prefix_error=False, + encoding="UTF-8") + except Exception: + log.error(traceback.format_exc()) + h.flash(_('error occurred during creation of repos group %s') \ + % request.POST.get('repos_group_name'), category='error') + + return redirect(url('repos_groups')) + + + @HasPermissionAnyDecorator('hg.admin') def new(self, format='html'): """GET /repos_groups/new: Form to create a new item""" # url('new_repos_group') + self.__load_defaults() + return render('admin/repos_groups/repos_groups_add.html') + @HasPermissionAnyDecorator('hg.admin') def update(self, id): """PUT /repos_groups/id: Update an existing item""" # Forms posted to this method should contain a hidden field: @@ -37,6 +102,7 @@ class ReposGroupsController(BaseControll # method='put') # url('repos_group', id=ID) + @HasPermissionAnyDecorator('hg.admin') def delete(self, id): """DELETE /repos_groups/id: Delete an existing item""" # Forms posted to this method should contain a hidden field: @@ -88,6 +154,7 @@ class ReposGroupsController(BaseControll return render('admin/repos_groups/repos_groups.html') + @HasPermissionAnyDecorator('hg.admin') def edit(self, id, format='html'): """GET /repos_groups/id/edit: Form to edit an existing item""" # url('edit_repos_group', id=ID) diff --git a/rhodecode/controllers/admin/settings.py b/rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py +++ b/rhodecode/controllers/admin/settings.py @@ -332,6 +332,8 @@ class SettingsController(BaseController) c.repo_groups.extend([(x.group_id, parents_link(x)) for \ x in self.sa.query(Group).all()]) + c.repo_groups = sorted(c.repo_groups, + key=lambda t: t[1].split('»')[0]) c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) new_repo = request.GET.get('repo', '') diff --git a/rhodecode/controllers/home.py b/rhodecode/controllers/home.py --- a/rhodecode/controllers/home.py +++ b/rhodecode/controllers/home.py @@ -66,8 +66,7 @@ class HomeController(BaseController): c.repo_cnt = len(c.repos_list) - c.groups = self.sa.query(Group)\ - .filter(Group.group_parent_id == None).all() + c.groups = Group.query().filter(Group.group_parent_id == None).all() return render('/index.html') diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -107,6 +107,11 @@ class RhodeCodeUi(Base): ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) + @classmethod + def get_by_key(cls, key): + return Session.query(cls).filter(cls.ui_key == key) + + class User(Base): __tablename__ = 'users' __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True}) @@ -296,7 +301,7 @@ class Repository(Base): class Group(Base): __tablename__ = 'groups' - __table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},) + __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'), {'useexisting':True},) __mapper_args__ = {'order_by':'group_name'} group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -35,7 +35,6 @@ from webhelpers.pylonslib.secure_form im 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 import meta from rhodecode.model.user import UserModel from rhodecode.model.repo import RepoModel from rhodecode.model.db import User, UsersGroup, Group @@ -117,6 +116,27 @@ def ValidUsersGroup(edit, old_data): 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('repos_group_name') + parent_id = value.get('repos_group_parent') + + # slugify repo group just in case :) + slug = repo_name_slug(group_name) + + # check filesystem + gr = Group.query().filter(Group.group_name == slug)\ + .filter(Group.group_parent_id == parent_id).scalar() + + if gr: + e_dict = {'repos_group_name':_('This group already exists')} + raise formencode.Invalid('', value, state, + error_dict=e_dict) + return _ValidReposGroup class ValidPassword(formencode.validators.FancyValidator): @@ -193,17 +213,13 @@ class ValidAuth(formencode.validators.Fa class ValidRepoUser(formencode.validators.FancyValidator): def to_python(self, value, state): - sa = meta.Session() try: - self.user_db = sa.query(User)\ + self.user_db = User.query()\ .filter(User.active == True)\ .filter(User.username == value).one() except Exception: raise formencode.Invalid(_('This username is not valid'), value, state) - finally: - meta.Session.remove() - return value def ValidRepoName(edit, old_data): @@ -222,6 +238,7 @@ def ValidRepoName(edit, old_data): gr = Group.get(value.get('repo_group')) group_path = gr.full_path # value needs to be aware of group name + # it has to use '/' repo_name_full = group_path + '/' + repo_name else: group_path = '' @@ -250,13 +267,13 @@ def ValidRepoName(edit, old_data): return _ValidRepoName -def SlugifyRepo(): - class _SlugifyRepo(formencode.validators.FancyValidator): +def SlugifyName(): + class _SlugifyName(formencode.validators.FancyValidator): def to_python(self, value, state): return repo_name_slug(value) - return _SlugifyRepo + return _SlugifyName def ValidCloneUri(): from mercurial.httprepo import httprepository, httpsrepository @@ -331,15 +348,14 @@ class ValidPerms(formencode.validators.F value['perms_new'] = perms_new #update permissions - sa = meta.Session for k, v, t in perms_new: try: if t is 'user': - self.user_db = sa.query(User)\ + self.user_db = User.query()\ .filter(User.active == True)\ .filter(User.username == k).one() if t is 'users_group': - self.user_db = sa.query(UsersGroup)\ + self.user_db = UsersGroup.query()\ .filter(UsersGroup.users_group_active == True)\ .filter(UsersGroup.users_group_name == k).one() @@ -373,15 +389,11 @@ def UniqSystemEmail(old_data): def to_python(self, value, state): value = value.lower() if old_data.get('email') != value: - sa = meta.Session() - try: - user = sa.query(User).filter(User.email == value).scalar() - if user: - raise formencode.Invalid(_("This e-mail address is already taken") , - value, state) - finally: - meta.Session.remove() - + user = User.query().filter(User.email == value).scalar() + if user: + raise formencode.Invalid( + _("This e-mail address is already taken"), + value, state) return value return _UniqSystemEmail @@ -389,14 +401,10 @@ def UniqSystemEmail(old_data): class ValidSystemEmail(formencode.validators.FancyValidator): def to_python(self, value, state): value = value.lower() - sa = meta.Session - try: - user = sa.query(User).filter(User.email == value).scalar() - if user is None: - raise formencode.Invalid(_("This e-mail address doesn't exist.") , - value, state) - finally: - meta.Session.remove() + user = User.query().filter(User.email == value).scalar() + if user is None: + raise formencode.Invalid(_("This e-mail address doesn't exist.") , + value, state) return value @@ -489,6 +497,23 @@ def UsersGroupForm(edit=False, old_data= return _UsersGroupForm +def ReposGroupForm(edit=False, old_data={}, available_groups=[]): + class _ReposGroupForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = True + + repos_group_name = All(UnicodeString(strip=True, min=1, not_empty=True), + SlugifyName()) + repos_group_description = UnicodeString(strip=True, min=1, + not_empty=True) + repos_group_parent = OneOf(available_groups, hideList=False, + testValueList=True, + if_missing=None, not_empty=False) + + chained_validators = [ValidReposGroup(edit, old_data)] + + return _ReposGroupForm + def RegisterForm(edit=False, old_data={}): class _RegisterForm(formencode.Schema): allow_extra_fields = True @@ -519,7 +544,7 @@ def RepoForm(edit=False, old_data={}, su allow_extra_fields = True filter_extra_fields = False repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyRepo()) + SlugifyName()) clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False), ValidCloneUri()()) repo_group = OneOf(repo_groups, hideList=True) @@ -541,7 +566,7 @@ def RepoForkForm(edit=False, old_data={} allow_extra_fields = True filter_extra_fields = False fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyRepo()) + SlugifyName()) description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) repo_type = All(ValidForkType(old_data), OneOf(supported_backends)) @@ -552,7 +577,7 @@ def RepoSettingsForm(edit=False, old_dat allow_extra_fields = True filter_extra_fields = False repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyRepo()) + SlugifyName()) description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) diff --git a/rhodecode/model/repos_group.py b/rhodecode/model/repos_group.py new file mode 100644 --- /dev/null +++ b/rhodecode/model/repos_group.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.model.user_group + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + users groups model for RhodeCode + + :created_on: Jan 25, 2011 + :author: marcink + :copyright: (C) 2009-2011 Marcin Kuzminski + :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 . + +import os +import logging +import traceback + +from pylons.i18n.translation import _ + +from vcs.utils.lazy import LazyProperty + +from rhodecode.model import BaseModel +from rhodecode.model.caching_query import FromCache +from rhodecode.model.db import Group, RhodeCodeUi + +log = logging.getLogger(__name__) + + +class ReposGroupModel(BaseModel): + + @LazyProperty + def repos_path(self): + """ + Get's the repositories root path from database + """ + + q = RhodeCodeUi.get_by_key('/').one() + return q.ui_value + + def __create_group(self, group_name, parent_id): + """ + makes repositories group on filesystem + + :param repo_name: + :param parent_id: + """ + + if parent_id: + parent_group_name = Group.get(parent_id).group_name + else: + parent_group_name = '' + + create_path = os.path.join(self.repos_path, parent_group_name, + group_name) + log.debug('creating new group in %s', create_path) + + if os.path.isdir(create_path): + raise Exception('That directory already exists !') + + + os.makedirs(create_path) + + + def __rename_group(self, group_name): + """ + Renames a group on filesystem + + :param group_name: + """ + pass + + def __delete_group(self, group_name): + """ + Deletes a group from a filesystem + + :param group_name: + """ + pass + + def create(self, form_data): + try: + new_repos_group = Group() + new_repos_group.group_name = form_data['repos_group_name'] + new_repos_group.group_description = \ + form_data['repos_group_description'] + new_repos_group.group_parent_id = form_data['repos_group_parent'] + + self.sa.add(new_repos_group) + + self.__create_group(form_data['repos_group_name'], + form_data['repos_group_parent']) + + self.sa.commit() + except: + log.error(traceback.format_exc()) + self.sa.rollback() + raise + + def update(self, repos_group_id, form_data): + + try: + repos_group = Group.get(repos_group_id) + + + + self.sa.add(repos_group) + self.sa.commit() + except: + log.error(traceback.format_exc()) + self.sa.rollback() + raise + + def delete(self, users_group_id): + try: + users_group = self.get(users_group_id, cache=False) + self.sa.delete(users_group) + self.sa.commit() + except: + log.error(traceback.format_exc()) + self.sa.rollback() + raise diff --git a/rhodecode/public/css/style.css b/rhodecode/public/css/style.css --- a/rhodecode/public/css/style.css +++ b/rhodecode/public/css/style.css @@ -477,6 +477,13 @@ margin:0; padding:12px 9px 7px 24px; } +#header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover { +background:url("../images/icons/database_link.png") no-repeat scroll 4px 9px #FFF; +width:167px; +margin:0; +padding:12px 9px 7px 24px; +} + #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover { background:#FFF url("../images/icons/user_edit.png") no-repeat 4px 9px; width:167px; diff --git a/rhodecode/templates/admin/repos/repo_add_base.html b/rhodecode/templates/admin/repos/repo_add_base.html --- a/rhodecode/templates/admin/repos/repo_add_base.html +++ b/rhodecode/templates/admin/repos/repo_add_base.html @@ -26,7 +26,6 @@
${h.select('repo_group','',c.repo_groups,class_="medium")} - ${h.link_to(_('add new group'),h.url(''))}
diff --git a/rhodecode/templates/admin/repos/repo_edit.html b/rhodecode/templates/admin/repos/repo_edit.html --- a/rhodecode/templates/admin/repos/repo_edit.html +++ b/rhodecode/templates/admin/repos/repo_edit.html @@ -49,7 +49,6 @@
${h.select('repo_group','',c.repo_groups,class_="medium")} - ${h.link_to(_('add new group'),h.url(''))}
diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_add.html b/rhodecode/templates/admin/repos_groups/repos_groups_add.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/admin/repos_groups/repos_groups_add.html @@ -0,0 +1,64 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('Add repos group')} - ${c.rhodecode_name} + +<%def name="breadcrumbs_links()"> + ${h.link_to(_('Admin'),h.url('admin_home'))} + » + ${h.link_to(_('Repos groups'),h.url('repos_groups'))} + » + ${_('add new repos group')} + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} +
+ + ${h.form(url('repos_groups'))} +
+ +
+
+
+ +
+
+ ${h.text('repos_group_name',class_='medium')} +
+
+ +
+
+ +
+
+ ${h.textarea('repos_group_description',cols=23,rows=5,class_="medium")} +
+
+ +
+
+ +
+
+ ${h.select('repos_group_parent','',c.repo_groups,class_="medium")} +
+
+ +
+ ${h.submit('save','save',class_="ui-button")} +
+
+
+ ${h.end_form()} +
+ \ No newline at end of file diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_show.html b/rhodecode/templates/admin/repos_groups/repos_groups_show.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/admin/repos_groups/repos_groups_show.html @@ -0,0 +1,68 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('Repositories groups administration')} - ${c.rhodecode_name} + + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_('Admin'),h.url('admin_home'))} » ${_('Repositories')} + +<%def name="page_nav()"> + ${self.menu('admin')} + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} + +
+ +
+ % if c.groups: + + + + + + + + + + + + ## REPO GROUPS + + % for gr in c.groups: + + + + + + + % endfor + +
${_('Group name')}${_('Description')}${_('Number of repositories')}${_('action')}
+
+ ${_('Repositories group')} + ${h.link_to(h.literal(' » '.join([g.group_name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))} +
+
${gr.group_description}${gr.repositories.count()} + ${h.form(url('repos_group', id=gr.group_id),method='delete')} + ${h.submit('remove_%s' % gr.group_name,'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this group');")} + ${h.end_form()} +
+ % else: + {_('There are no repositories groups yet')} + % endif + +
+
+ + diff --git a/rhodecode/templates/base/base.html b/rhodecode/templates/base/base.html --- a/rhodecode/templates/base/base.html +++ b/rhodecode/templates/base/base.html @@ -322,6 +322,7 @@
  • ${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}
  • ${h.link_to(_('repositories'),h.url('repos'),class_='repos')}
  • +
  • ${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}
  • ${h.link_to(_('users'),h.url('users'),class_='users')}
  • ${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}
  • ${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}