changeset 1345:3bce31f026b8 beta

#47 implemented Adding of new repo_groups+forms+validators. Fixed sorting of repo groups by main names in multiple locations. Removed some unneeded calls to self.sa for exchange to .query() methods. Added new db unique key for Group
author Marcin Kuzminski <marcin@python-works.com>
date Mon, 23 May 2011 02:22:00 +0200
parents eef7a1b953e8
children cf78d302d441
files rhodecode/controllers/admin/repos.py rhodecode/controllers/admin/repos_groups.py rhodecode/controllers/admin/settings.py rhodecode/controllers/home.py rhodecode/model/db.py rhodecode/model/forms.py rhodecode/model/repos_group.py rhodecode/public/css/style.css rhodecode/templates/admin/repos/repo_add_base.html rhodecode/templates/admin/repos/repo_edit.html rhodecode/templates/admin/repos_groups/repos_groups_add.html rhodecode/templates/admin/repos_groups/repos_groups_show.html rhodecode/templates/base/base.html
diffstat 13 files changed, 408 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/controllers/admin/repos.py	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/controllers/admin/repos.py	Mon May 23 02:22:00 2011 +0200
@@ -74,6 +74,8 @@
 
         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('&raquo;')[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()
--- a/rhodecode/controllers/admin/repos_groups.py	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/controllers/admin/repos_groups.py	Mon May 23 02:22:00 2011 +0200
@@ -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 @@
     # file has a resource setup:
     #     map.resource('repos_group', 'repos_groups')
 
+    def __load_defaults(self):
+
+        c.repo_groups = [('', '')]
+        parents_link = lambda k: h.literal('&raquo;'.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('&raquo;')[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 @@
         #           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 @@
 
         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)
--- a/rhodecode/controllers/admin/settings.py	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/controllers/admin/settings.py	Mon May 23 02:22:00 2011 +0200
@@ -332,6 +332,8 @@
 
         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('&raquo;')[0])
         c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
 
         new_repo = request.GET.get('repo', '')
--- a/rhodecode/controllers/home.py	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/controllers/home.py	Mon May 23 02:22:00 2011 +0200
@@ -66,8 +66,7 @@
         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')
--- a/rhodecode/model/db.py	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/model/db.py	Mon May 23 02:22:00 2011 +0200
@@ -107,6 +107,11 @@
     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 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)
--- a/rhodecode/model/forms.py	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/model/forms.py	Mon May 23 02:22:00 2011 +0200
@@ -35,7 +35,6 @@
 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 @@
     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 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 @@
                 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 @@
 
     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 @@
         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 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 @@
 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 @@
 
     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 @@
         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 @@
         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 @@
         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)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/model/repos_group.py	Mon May 23 02:22:00 2011 +0200
@@ -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 <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 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
--- a/rhodecode/public/css/style.css	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/public/css/style.css	Mon May 23 02:22:00 2011 +0200
@@ -477,6 +477,13 @@
 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;
--- a/rhodecode/templates/admin/repos/repo_add_base.html	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/templates/admin/repos/repo_add_base.html	Mon May 23 02:22:00 2011 +0200
@@ -26,7 +26,6 @@
              </div>
              <div class="input">
                  ${h.select('repo_group','',c.repo_groups,class_="medium")}
-             <span>${h.link_to(_('add new group'),h.url(''))}</span>
              </div>
          </div>         
         <div class="field">
--- a/rhodecode/templates/admin/repos/repo_edit.html	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/templates/admin/repos/repo_edit.html	Mon May 23 02:22:00 2011 +0200
@@ -49,7 +49,6 @@
 	            </div>
 	            <div class="input">
 	                ${h.select('repo_group','',c.repo_groups,class_="medium")}
-	            <span>${h.link_to(_('add new group'),h.url(''))}</span>
 	            </div>
 	        </div>         
             <div class="field">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos_groups/repos_groups_add.html	Mon May 23 02:22:00 2011 +0200
@@ -0,0 +1,64 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('Add repos group')} - ${c.rhodecode_name}
+</%def>
+<%def name="breadcrumbs_links()">
+    ${h.link_to(_('Admin'),h.url('admin_home'))} 
+    &raquo; 
+    ${h.link_to(_('Repos groups'),h.url('repos_groups'))} 
+    &raquo;
+    ${_('add new repos group')}
+</%def>
+
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}       
+    </div>
+    <!-- end box / title -->
+    ${h.form(url('repos_groups'))}
+    <div class="form">
+        <!-- fields -->
+        <div class="fields">
+             <div class="field">
+                <div class="label">
+                    <label for="users_group_name">${_('Group name')}:</label>
+                </div>
+                <div class="input">
+                    ${h.text('repos_group_name',class_='medium')}
+                </div>
+             </div>
+             
+	        <div class="field">
+	            <div class="label label-textarea">
+	                <label for="description">${_('Description')}:</label>
+	            </div>
+	            <div class="textarea text-area editor">
+	                ${h.textarea('repos_group_description',cols=23,rows=5,class_="medium")}
+	            </div>
+	         </div>
+             
+	         <div class="field">
+	             <div class="label">
+	                 <label for="repo_group">${_('Group parent')}:</label>
+	             </div>
+	             <div class="input">
+	                 ${h.select('repos_group_parent','',c.repo_groups,class_="medium")}
+	             </div>
+	         </div>             
+                         
+            <div class="buttons">
+              ${h.submit('save','save',class_="ui-button")}
+            </div>             
+        </div>
+    </div>
+    ${h.end_form()}
+</div>    
+</%def>    
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos_groups/repos_groups_show.html	Mon May 23 02:22:00 2011 +0200
@@ -0,0 +1,68 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('Repositories groups administration')} - ${c.rhodecode_name}
+</%def>
+
+
+<%def name="breadcrumbs_links()">
+    ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
+</%def>
+<%def name="page_nav()">
+    ${self.menu('admin')}
+</%def>
+<%def name="main()">
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+        <ul class="links">
+          <li>
+            <span>${h.link_to(u'ADD NEW GROUP',h.url('new_repos_group'))}</span>
+          </li>          
+        </ul>        
+    </div>
+    <!-- end box / title -->
+    <div class="table">
+           % if c.groups:
+            <table class="table_disp">
+            
+                <thead>
+                    <tr>
+                        <th class="left"><a href="#">${_('Group name')}</a></th>
+                        <th class="left"><a href="#">${_('Description')}</a></th>
+                        <th class="left"><a href="#">${_('Number of repositories')}</a></th>
+                        <th class="left">${_('action')}</th>
+                    </tr>
+                </thead>
+                
+                ## REPO GROUPS
+                
+                % for gr in c.groups:
+                  <tr>
+                      <td>
+                          <div style="white-space: nowrap">
+                          <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
+                          ${h.link_to(h.literal(' &raquo; '.join([g.group_name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))}
+                          </div>
+                      </td>
+                      <td>${gr.group_description}</td>
+                      <td><b>${gr.repositories.count()}</b></td>
+		               <td>
+		                 ${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()}
+		               </td>                      
+                  </tr>
+                % endfor
+                
+            </table>
+            % else:
+                {_('There are no repositories groups yet')}
+            % endif
+         
+    </div>
+</div> 
+           
+</%def>    
--- a/rhodecode/templates/base/base.html	Mon May 23 00:18:32 2011 +0200
+++ b/rhodecode/templates/base/base.html	Mon May 23 02:22:00 2011 +0200
@@ -322,6 +322,7 @@
                         <ul>
                             <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
                             <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
+                            <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
                             <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
                             <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
                             <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>