repo-groups: implemented default personal repo groups logic.

- fixes #4003
- adds new settings to automatically create personal repo groups for users on API cal, add user form, registration
- it's possible to create a pattern based repo group for example /u/{username}
This commit is contained in:
Marcin Kuzminski 2016-11-07 15:27:36 +01:00
parent 922242c2ba
commit 9e6713219c
19 changed files with 240 additions and 56 deletions

View file

@ -51,7 +51,7 @@ PYRAMID_SETTINGS = {}
EXTENSIONS = {}
__version__ = ('.'.join((str(each) for each in VERSION[:3])))
__dbversion__ = 62 # defines current db version for migrations
__dbversion__ = 63 # defines current db version for migrations
__platform__ = platform.system()
__license__ = 'AGPLv3, and Commercial License'
__author__ = 'RhodeCode GmbH'

View file

@ -25,7 +25,7 @@ from rhodecode.api.utils import (
Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
from rhodecode.lib.auth import AuthUser, PasswordGenerator
from rhodecode.lib.exceptions import DefaultUserException
from rhodecode.lib.utils2 import safe_int
from rhodecode.lib.utils2 import safe_int, str2bool
from rhodecode.model.db import Session, User, Repository
from rhodecode.model.user import UserModel
@ -155,7 +155,8 @@ def create_user(request, apiuser, username, email, password=Optional(''),
active=Optional(True), admin=Optional(False),
extern_name=Optional('rhodecode'),
extern_type=Optional('rhodecode'),
force_password_change=Optional(False)):
force_password_change=Optional(False),
create_personal_repo_group=Optional(None)):
"""
Creates a new user and returns the new user object.
@ -188,7 +189,8 @@ def create_user(request, apiuser, username, email, password=Optional(''),
:param force_password_change: Force the new user to change password
on next login.
:type force_password_change: Optional(``True`` | ``False``)
:param create_personal_repo_group: Create personal repo group for this user
:type create_personal_repo_group: Optional(``True`` | ``False``)
Example output:
.. code-block:: bash
@ -230,6 +232,9 @@ def create_user(request, apiuser, username, email, password=Optional(''),
Optional.extract(extern_name) != 'rhodecode'):
# generate temporary password if user is external
password = PasswordGenerator().gen_password(length=16)
create_repo_group = Optional.extract(create_personal_repo_group)
if isinstance(create_repo_group, basestring):
create_repo_group = str2bool(create_repo_group)
try:
user = UserModel().create_or_update(
@ -243,6 +248,7 @@ def create_user(request, apiuser, username, email, password=Optional(''),
extern_type=Optional.extract(extern_type),
extern_name=Optional.extract(extern_name),
force_password_change=Optional.extract(force_password_change),
create_repo_group=create_repo_group
)
Session().commit()
return {

View file

@ -160,6 +160,7 @@ class ReposController(BaseRepoController):
self.__load_defaults()
form_result = {}
task_id = None
c.personal_repo_group = c.rhodecode_user.personal_repo_group
try:
# CanWriteToGroup validators checks permissions of this POST
form_result = RepoForm(repo_groups=c.repo_groups_choices,
@ -173,8 +174,6 @@ class ReposController(BaseRepoController):
if isinstance(task, BaseAsyncResult):
task_id = task.task_id
except formencode.Invalid as errors:
c.personal_repo_group = RepoGroup.get_by_group_name(
c.rhodecode_user.username)
return htmlfill.render(
render('admin/repos/repo_add.html'),
defaults=errors.value,
@ -215,7 +214,7 @@ class ReposController(BaseRepoController):
c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
choices, c.landing_revs = ScmModel().get_repo_landing_revs()
c.personal_repo_group = RepoGroup.get_by_group_name(c.rhodecode_user.username)
c.personal_repo_group = c.rhodecode_user.personal_repo_group
c.new_repo = repo_name_slug(new_repo)
## apply the defaults from defaults page
@ -299,9 +298,8 @@ class ReposController(BaseRepoController):
repo_model = RepoModel()
changed_name = repo_name
c.personal_repo_group = c.rhodecode_user.personal_repo_group
# override the choices with extracted revisions !
c.personal_repo_group = RepoGroup.get_by_group_name(
c.rhodecode_user.username)
repo = Repository.get_by_repo_name(repo_name)
old_data = {
'repo_name': repo_name,
@ -399,8 +397,7 @@ class ReposController(BaseRepoController):
c.repo_fields = RepositoryField.query()\
.filter(RepositoryField.repository == c.repo_info).all()
c.personal_repo_group = RepoGroup.get_by_group_name(
c.rhodecode_user.username)
c.personal_repo_group = c.rhodecode_user.personal_repo_group
c.active = 'settings'
return htmlfill.render(
render('admin/repos/repo_edit.html'),

View file

@ -55,6 +55,7 @@ from rhodecode.model.db import RhodeCodeUi, Repository
from rhodecode.model.forms import ApplicationSettingsForm, \
ApplicationUiSettingsForm, ApplicationVisualisationForm, \
LabsSettingsForm, IssueTrackerPatternsForm
from rhodecode.model.repo_group import RepoGroupModel
from rhodecode.model.scm import ScmModel
from rhodecode.model.notification import EmailNotificationModel
@ -245,6 +246,8 @@ class SettingsController(BaseController):
"""POST /admin/settings/global: All items in the collection"""
# url('admin_settings_global')
c.active = 'global'
c.personal_repo_group_default_pattern = RepoGroupModel()\
.get_personal_group_name_pattern()
application_form = ApplicationSettingsForm()()
try:
form_result = application_form.to_python(dict(request.POST))
@ -259,16 +262,18 @@ class SettingsController(BaseController):
try:
settings = [
('title', 'rhodecode_title'),
('realm', 'rhodecode_realm'),
('pre_code', 'rhodecode_pre_code'),
('post_code', 'rhodecode_post_code'),
('captcha_public_key', 'rhodecode_captcha_public_key'),
('captcha_private_key', 'rhodecode_captcha_private_key'),
('title', 'rhodecode_title', 'unicode'),
('realm', 'rhodecode_realm', 'unicode'),
('pre_code', 'rhodecode_pre_code', 'unicode'),
('post_code', 'rhodecode_post_code', 'unicode'),
('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
]
for setting, form_key in settings:
for setting, form_key, type_ in settings:
sett = SettingsModel().create_or_update_setting(
setting, form_result[form_key])
setting, form_result[form_key], type_)
Session().add(sett)
Session().commit()
@ -287,6 +292,8 @@ class SettingsController(BaseController):
"""GET /admin/settings/global: All items in the collection"""
# url('admin_settings_global')
c.active = 'global'
c.personal_repo_group_default_pattern = RepoGroupModel()\
.get_personal_group_name_pattern()
return htmlfill.render(
render('admin/settings/settings.html'),

View file

@ -45,12 +45,13 @@ from rhodecode.model.db import (
PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
from rhodecode.model.forms import (
UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
from rhodecode.model.repo_group import RepoGroupModel
from rhodecode.model.user import UserModel
from rhodecode.model.meta import Session
from rhodecode.model.permission import PermissionModel
from rhodecode.lib.utils import action_logger
from rhodecode.lib.ext_json import json
from rhodecode.lib.utils2 import datetime_to_time, safe_int
from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
log = logging.getLogger(__name__)
@ -120,6 +121,16 @@ class UsersController(BaseController):
c.data = json.dumps(users_data)
return render('admin/users/users.html')
def _get_personal_repo_group_template_vars(self):
DummyUser = AttributeDict({
'username': '${username}',
'user_id': '${user_id}',
})
c.default_create_repo_group = RepoGroupModel() \
.get_default_create_personal_repo_group()
c.personal_repo_group_name = RepoGroupModel() \
.get_personal_group_name(DummyUser)
@HasPermissionAllDecorator('hg.admin')
@auth.CSRFRequired()
def create(self):
@ -143,6 +154,7 @@ class UsersController(BaseController):
% {'user_link': user_link}), category='success')
Session().commit()
except formencode.Invalid as errors:
self._get_personal_repo_group_template_vars()
return htmlfill.render(
render('admin/users/user_add.html'),
defaults=errors.value,
@ -163,6 +175,7 @@ class UsersController(BaseController):
"""GET /users/new: Form to create a new item"""
# url('new_user')
c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
self._get_personal_repo_group_template_vars()
return render('admin/users/user_add.html')
@HasPermissionAllDecorator('hg.admin')
@ -339,22 +352,41 @@ class UsersController(BaseController):
user_id = safe_int(user_id)
c.user = User.get_or_404(user_id)
personal_repo_group = RepoGroup.get_user_personal_repo_group(
c.user.user_id)
if personal_repo_group:
return redirect(url('edit_user_advanced', user_id=user_id))
personal_repo_group_name = RepoGroupModel().get_personal_group_name(
c.user)
named_personal_group = RepoGroup.get_by_group_name(
personal_repo_group_name)
try:
desc = RepoGroupModel.PERSONAL_GROUP_DESC % {
'username': c.user.username}
if not RepoGroup.get_by_group_name(c.user.username):
RepoGroupModel().create(group_name=c.user.username,
group_description=desc,
owner=c.user.username)
msg = _('Created repository group `%s`' % (c.user.username,))
if named_personal_group and named_personal_group.user_id == c.user.user_id:
# migrate the same named group, and mark it as personal
named_personal_group.personal = True
Session().add(named_personal_group)
Session().commit()
msg = _('Linked repository group `%s` as personal' % (
personal_repo_group_name,))
h.flash(msg, category='success')
elif not named_personal_group:
RepoGroupModel().create_personal_repo_group(c.user)
msg = _('Created repository group `%s`' % (
personal_repo_group_name,))
h.flash(msg, category='success')
else:
msg = _('Repository group `%s` is already taken' % (
personal_repo_group_name,))
h.flash(msg, category='warning')
except Exception:
log.exception("Exception during repository group creation")
msg = _(
'An error occurred during repository group creation for user')
h.flash(msg, category='error')
Session().rollback()
return redirect(url('edit_user_advanced', user_id=user_id))
@ -397,7 +429,9 @@ class UsersController(BaseController):
c.active = 'advanced'
c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
c.personal_repo_group = RepoGroup.get_by_group_name(user.username)
c.personal_repo_group = c.perm_user.personal_repo_group
c.personal_repo_group_name = RepoGroupModel()\
.get_personal_group_name(user)
c.first_admin = User.get_first_super_admin()
defaults = user.get_dict()

View file

@ -60,8 +60,7 @@ class ForksController(BaseRepoController):
c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
choices, c.landing_revs = ScmModel().get_repo_landing_revs()
c.landing_revs_choices = choices
c.personal_repo_group = RepoGroup.get_by_group_name(
c.rhodecode_user.username)
c.personal_repo_group = c.rhodecode_user.personal_repo_group
def __load_data(self, repo_name=None):
"""

View file

@ -48,6 +48,7 @@ from rhodecode.events.base import RhodecodeEvent
from rhodecode.events.user import ( # noqa
UserPreCreate,
UserPostCreate,
UserPreUpdate,
UserRegistered
)

View file

@ -51,6 +51,19 @@ class UserPreCreate(RhodecodeEvent):
self.user_data = user_data
@implementer(IUserPreCreate)
class UserPostCreate(RhodecodeEvent):
"""
An instance of this class is emitted as an :term:`event` after a new user
object is created.
"""
name = 'user-post-create'
display_name = lazy_ugettext('user post create')
def __init__(self, user_data):
self.user_data = user_data
@implementer(IUserPreUpdate)
class UserPreUpdate(RhodecodeEvent):
"""

View file

@ -49,7 +49,7 @@ from rhodecode.model.meta import Session
from rhodecode.model.user import UserModel
from rhodecode.model.db import (
User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
UserIpMap, UserApiKeys)
UserIpMap, UserApiKeys, RepoGroup)
from rhodecode.lib import caches
from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
from rhodecode.lib.utils import (
@ -983,6 +983,9 @@ class AuthUser(object):
inherit = self.inherit_default_permissions
return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
inherit_from_default=inherit)
@property
def personal_repo_group(self):
return RepoGroup.get_user_personal_repo_group(self.user_id)
@classmethod
def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):

View file

@ -0,0 +1,27 @@
import logging
from sqlalchemy import Column, MetaData, Boolean
from rhodecode.lib.dbmigrate.versions import _reset_base
log = logging.getLogger(__name__)
def upgrade(migrate_engine):
"""
Upgrade operations go here.
Don't create your own engine; bind migrate_engine to your metadata
"""
_reset_base(migrate_engine)
from rhodecode.lib.dbmigrate.schema import db_4_5_0_0 as db
# Add personal column to RepoGroup table.
rg_table = db.RepoGroup.__table__
rg_col = Column(
'personal', Boolean(), nullable=True, unique=None, default=None)
rg_col.create(table=rg_table)
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine

View file

@ -96,7 +96,7 @@ def __get_lem(extra_mapping=None):
def str2bool(_str):
"""
returs True/False value from given string, it tries to translate the
returns True/False value from given string, it tries to translate the
string into boolean
:param _str: string value to translate into boolean

View file

@ -2028,6 +2028,7 @@ class RepoGroup(Base, BaseModel):
enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
personal = Column('personal', Boolean(), 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('UserGroupRepoGroupToPerm', cascade='all')
@ -2082,6 +2083,13 @@ class RepoGroup(Base, BaseModel):
"get_group_%s" % _hash_key(group_name)))
return gr.scalar()
@classmethod
def get_user_personal_repo_group(cls, user_id):
user = User.get(user_id)
return cls.query()\
.filter(cls.personal == true())\
.filter(cls.user == user).scalar()
@classmethod
def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
case_insensitive=True):

View file

@ -341,6 +341,8 @@ def ApplicationSettingsForm():
rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
return _ApplicationSettingsForm

View file

@ -23,13 +23,13 @@
repo group model for RhodeCode
"""
import os
import datetime
import itertools
import logging
import os
import shutil
import traceback
import string
from zope.cachedescriptors.property import Lazy as LazyProperty
@ -38,7 +38,7 @@ from rhodecode.model import BaseModel
from rhodecode.model.db import (
RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
UserGroup, Repository)
from rhodecode.model.settings import VcsSettingsModel
from rhodecode.model.settings import VcsSettingsModel, SettingsModel
from rhodecode.lib.caching_query import FromCache
from rhodecode.lib.utils2 import action_logger_generic
@ -49,6 +49,7 @@ class RepoGroupModel(BaseModel):
cls = RepoGroup
PERSONAL_GROUP_DESC = '[personal] repo group: owner `%(username)s`'
PERSONAL_GROUP_PATTERN = '${username}' # default
def _get_user_group(self, users_group):
return self._get_instance(UserGroup, users_group,
@ -76,6 +77,39 @@ class RepoGroupModel(BaseModel):
"sql_cache_short", "get_repo_group_%s" % repo_group_name))
return repo.scalar()
def get_default_create_personal_repo_group(self):
value = SettingsModel().get_setting_by_name(
'create_personal_repo_group')
return value.app_settings_value if value else None or False
def get_personal_group_name_pattern(self):
value = SettingsModel().get_setting_by_name(
'personal_repo_group_pattern')
val = value.app_settings_value if value else None
group_template = val or self.PERSONAL_GROUP_PATTERN
group_template = group_template.lstrip('/')
return group_template
def get_personal_group_name(self, user):
template = self.get_personal_group_name_pattern()
return string.Template(template).safe_substitute(
username=user.username,
user_id=user.user_id,
)
def create_personal_repo_group(self, user, commit_early=True):
desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
personal_repo_group_name = self.get_personal_group_name(user)
# create a new one
RepoGroupModel().create(
group_name=personal_repo_group_name,
group_description=desc,
owner=user.username,
personal=True,
commit_early=commit_early)
def _create_default_perms(self, new_group):
# create default permission
default_perm = 'group.read'
@ -191,7 +225,7 @@ class RepoGroupModel(BaseModel):
shutil.move(rm_path, os.path.join(self.repos_path, _d))
def create(self, group_name, group_description, owner, just_db=False,
copy_permissions=False, commit_early=True):
copy_permissions=False, personal=None, commit_early=True):
(group_name_cleaned,
parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
@ -199,11 +233,18 @@ class RepoGroupModel(BaseModel):
parent_group = None
if parent_group_name:
parent_group = self._get_repo_group(parent_group_name)
if not parent_group:
# we tried to create a nested group, but the parent is not
# existing
raise ValueError(
'Parent group `%s` given in `%s` group name '
'is not yet existing.' % (parent_group_name, group_name))
# becase we are doing a cleanup, we need to check if such directory
# already exists. If we don't do that we can accidentally delete existing
# directory via cleanup that can cause data issues, since delete does a
# folder rename to special syntax later cleanup functions can delete this
# because we are doing a cleanup, we need to check if such directory
# already exists. If we don't do that we can accidentally delete
# existing directory via cleanup that can cause data issues, since
# delete does a folder rename to special syntax later cleanup
# functions can delete this
cleanup_group = self.check_exist_filesystem(group_name,
exc_on_failure=False)
try:
@ -213,6 +254,7 @@ class RepoGroupModel(BaseModel):
new_repo_group.group_description = group_description or group_name
new_repo_group.parent_group = parent_group
new_repo_group.group_name = group_name
new_repo_group.personal = personal
self.sa.add(new_repo_group)

View file

@ -35,7 +35,7 @@ from sqlalchemy.sql.expression import true, false
from rhodecode import events
from rhodecode.lib.utils2 import (
safe_unicode, get_current_rhodecode_user, action_logger_generic,
AttributeDict)
AttributeDict, str2bool)
from rhodecode.lib.caching_query import FromCache
from rhodecode.model import BaseModel
from rhodecode.model.auth_token import AuthTokenModel
@ -104,12 +104,13 @@ class UserModel(BaseModel):
'cur_user': cur_user
}
if 'create_repo_group' in form_data:
user_data['create_repo_group'] = str2bool(
form_data.get('create_repo_group'))
try:
if form_data.get('create_repo_group'):
user_data['create_repo_group'] = True
if form_data.get('password_change'):
user_data['force_password_change'] = True
return UserModel().create_or_update(**user_data)
except Exception:
log.error(traceback.format_exc())
@ -177,7 +178,7 @@ class UserModel(BaseModel):
self, username, password, email, firstname='', lastname='',
active=True, admin=False, extern_type=None, extern_name=None,
cur_user=None, plugin=None, force_password_change=False,
allow_to_create_user=True, create_repo_group=False,
allow_to_create_user=True, create_repo_group=None,
updating_user_id=None, language=None, strict_creation_check=True):
"""
Creates a new instance if not found, or updates current one
@ -222,8 +223,8 @@ class UserModel(BaseModel):
# in case it's a plugin we don't care
if not plugin:
# first check if we gave crypted password back, and if it matches
# it's not password change
# first check if we gave crypted password back, and if it
# matches it's not password change
if new_user.password == password:
return False
@ -233,6 +234,12 @@ class UserModel(BaseModel):
return False
# read settings on default personal repo group creation
if create_repo_group is None:
default_create_repo_group = RepoGroupModel()\
.get_default_create_personal_repo_group()
create_repo_group = default_create_repo_group
user_data = {
'username': username,
'password': password,
@ -319,17 +326,16 @@ class UserModel(BaseModel):
self.sa.add(new_user)
if not edit and create_repo_group:
# create new group same as username, and make this user an owner
desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
RepoGroupModel().create(group_name=username,
group_description=desc,
owner=username, commit_early=False)
RepoGroupModel().create_personal_repo_group(
new_user, commit_early=False)
if not edit:
# add the RSS token
AuthTokenModel().create(username,
description='Generated feed token',
role=AuthTokenModel.cls.ROLE_FEED)
log_create_user(created_by=cur_user, **new_user.get_dict())
events.trigger(events.UserPostCreate(user_data))
return new_user
except (DatabaseError,):
log.error(traceback.format_exc())

View file

@ -30,6 +30,35 @@ ${h.secure_form(url('admin_settings_global'), method='post')}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" id="personal-group-options">
<h3 class="panel-title">${_('Personal Repository Group')} <a class="permalink" href="#personal-group-options"></a></h3>
</div>
<div class="panel-body">
<div class="checkbox">
${h.checkbox('rhodecode_create_personal_repo_group','True')}
<label for="rhodecode_create_personal_repo_group">${_('Create Personal Repository Group')}</label>
</div>
<span class="help-block">
${_('Always create Personal Repository Groups for new users.')} <br/>
${_('When creating new users from add user form or API you can still turn this off via a checkbox or flag')}
</span>
<div class="label">
<label for="rhodecode_personal_repo_group_pattern">${_('Personal Repo Group Pattern')}</label>
</div>
<div class="field input">
${h.text('rhodecode_personal_repo_group_pattern',size=60, placeholder=c.personal_repo_group_default_pattern)}
</div>
<span class="help-block">
${_('Pattern used to create Personal Repository Groups. Prefix can be other existing repository group path[s], eg. /u/${username}')} <br/>
${_('Available variables are currently ${username} and ${user_id}')}
</span>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" id="captcha-options">
<h3 class="panel-title">${_('Registration Captcha')} <a class="permalink" href="#captcha-options"></a></h3>

View file

@ -113,11 +113,14 @@
<div class="field">
<div class="label label-checkbox">
<label for="create_repo_group">${_('Add repository group')}:</label>
<label for="create_repo_group">${_('Add personal repository group')}:</label>
</div>
<div class="checkboxes">
${h.checkbox('create_repo_group',value=True)}
<span class="help-block">${_('Add repository group with the same name as username. \nUser will be automatically set as this group owner.')}</span>
${h.checkbox('create_repo_group',value=True, checked=c.default_create_repo_group)}
<span class="help-block">
${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}<br/>
${_('User will be automatically set as this group owner.')}
</span>
</div>
</div>

View file

@ -61,7 +61,11 @@
%if c.personal_repo_group:
<div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, url('repo_group_home', group_name=c.personal_repo_group.group_name))}</div>
%else:
<div class="panel-body-title-text">${_('This user currently does not have a personal repository group')}</div>
<div class="panel-body-title-text">
${_('This user currently does not have a personal repository group')}
<br/>
${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
</div>
%endif
<button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
<i class="icon-folder-close"></i>

View file

@ -338,6 +338,9 @@
<div class="">
<ol class="links">
<li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
% if c.rhodecode_user.personal_repo_group:
<li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
% endif
<li class="logout">
${h.secure_form(h.route_path('logout'))}
${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}