##// END OF EJS Templates
caches: use repo.lru based Dict cache. This LRUDict uses Timing Algo to not have to use locking...
caches: use repo.lru based Dict cache. This LRUDict uses Timing Algo to not have to use locking for the LRU implementation, this it's safer to use for dogpile. We used it before with beaker, so it's generally more stable.

File last commit:

r2592:0e0508de default
r2945:ec5716e4 default
Show More
my_account.py
604 lines | 22.6 KiB | text/x-python | PythonLexer
notifications: ported views to pyramid
r1920 # -*- coding: utf-8 -*-
release: update copyright year to 2018
r2487 # Copyright (C) 2016-2018 RhodeCode GmbH
notifications: ported views to pyramid
r1920 #
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# 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 Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import logging
import datetime
import formencode
apps: cleanup imports
r2080 import formencode.htmlfill
notifications: ported views to pyramid
r1920 from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
from pyramid.renderers import render
from pyramid.response import Response
from rhodecode.apps._base import BaseAppView, DataGridAppView
from rhodecode import forms
from rhodecode.lib import helpers as h
from rhodecode.lib import audit_logger
from rhodecode.lib.ext_json import json
from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
apps: cleanup imports
r2080 from rhodecode.lib.channelstream import (
channelstream_request, ChannelstreamException)
notifications: ported views to pyramid
r1920 from rhodecode.lib.utils2 import safe_int, md5, str2bool
from rhodecode.model.auth_token import AuthTokenModel
from rhodecode.model.comment import CommentsModel
from rhodecode.model.db import (
Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
PullRequest)
pylons: remove pylons as dependency...
r2351 from rhodecode.model.forms import UserForm, UserExtraEmailForm
notifications: ported views to pyramid
r1920 from rhodecode.model.meta import Session
from rhodecode.model.pull_request import PullRequestModel
from rhodecode.model.scm import RepoList
from rhodecode.model.user import UserModel
from rhodecode.model.repo import RepoModel
user-account: expose membership of user groups to users....
r2496 from rhodecode.model.user_group import UserGroupModel
notifications: ported views to pyramid
r1920 from rhodecode.model.validation_schema.schemas import user_schema
log = logging.getLogger(__name__)
class MyAccountView(BaseAppView, DataGridAppView):
ALLOW_SCOPED_TOKENS = False
"""
This view has alternative version inside EE, if modified please take a look
in there as well.
"""
def load_default_context(self):
c = self._get_local_tmpl_context()
c.user = c.auth_user.get_instance()
c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
pylons: remove pylons as dependency...
r2351
notifications: ported views to pyramid
r1920 return c
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_profile', request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_profile(self):
c = self.load_default_context()
c.active = 'profile'
return self._get_template_context(c)
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_password', request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_password(self):
c = self.load_default_context()
c.active = 'password'
c.extern_type = c.user.extern_type
schema = user_schema.ChangePasswordSchema().bind(
username=c.user.username)
form = forms.Form(
views: fixed some view names for better usage in view whitelist access
r1944 schema,
action=h.route_path('my_account_password_update'),
buttons=(forms.buttons.save, forms.buttons.reset))
notifications: ported views to pyramid
r1920
c.form = form
return self._get_template_context(c)
@LoginRequired()
@NotAnonymous()
@CSRFRequired()
@view_config(
views: fixed some view names for better usage in view whitelist access
r1944 route_name='my_account_password_update', request_method='POST',
notifications: ported views to pyramid
r1920 renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_password_update(self):
_ = self.request.translate
c = self.load_default_context()
c.active = 'password'
c.extern_type = c.user.extern_type
schema = user_schema.ChangePasswordSchema().bind(
username=c.user.username)
form = forms.Form(
schema, buttons=(forms.buttons.save, forms.buttons.reset))
if c.extern_type != 'rhodecode':
raise HTTPFound(self.request.route_path('my_account_password'))
controls = self.request.POST.items()
try:
valid_data = form.validate(controls)
UserModel().update_user(c.user.user_id, **valid_data)
c.user.update_userdata(force_password_change=False)
Session().commit()
except forms.ValidationFailure as e:
c.form = e
return self._get_template_context(c)
except Exception:
log.exception("Exception updating password")
h.flash(_('Error occurred during update of user password'),
category='error')
else:
instance = c.auth_user.get_instance()
self.session.setdefault('rhodecode_user', {}).update(
{'password': md5(instance.password)})
self.session.save()
h.flash(_("Successfully updated password"), category='success')
raise HTTPFound(self.request.route_path('my_account_password'))
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_auth_tokens', request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_auth_tokens(self):
_ = self.request.translate
c = self.load_default_context()
c.active = 'auth_tokens'
auth-tokens: allow specifing custom expiration date manually....
r2083 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
notifications: ported views to pyramid
r1920 c.role_values = [
(x, AuthTokenModel.cls._get_role_name(x))
for x in AuthTokenModel.cls.ROLES]
c.role_options = [(c.role_values, _("Role"))]
c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
c.user.user_id, show_expired=True)
repo-auth-tokens: UX, set and disable to VCS scope if selected an repo from select2
r2118 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
notifications: ported views to pyramid
r1920 return self._get_template_context(c)
def maybe_attach_token_scope(self, token):
# implemented in EE edition
pass
@LoginRequired()
@NotAnonymous()
@CSRFRequired()
@view_config(
route_name='my_account_auth_tokens_add', request_method='POST',)
def my_account_auth_tokens_add(self):
_ = self.request.translate
c = self.load_default_context()
lifetime = safe_int(self.request.POST.get('lifetime'), -1)
description = self.request.POST.get('description')
role = self.request.POST.get('role')
token = AuthTokenModel().create(
c.user.user_id, description, lifetime, role)
token_data = token.get_api_data()
self.maybe_attach_token_scope(token)
audit_logger.store_web(
'user.edit.token.add', action_data={
'data': {'token': token_data, 'user': 'self'}},
user=self._rhodecode_user, )
Session().commit()
h.flash(_("Auth token successfully created"), category='success')
return HTTPFound(h.route_path('my_account_auth_tokens'))
@LoginRequired()
@NotAnonymous()
@CSRFRequired()
@view_config(
route_name='my_account_auth_tokens_delete', request_method='POST')
def my_account_auth_tokens_delete(self):
_ = self.request.translate
c = self.load_default_context()
del_auth_token = self.request.POST.get('del_auth_token')
if del_auth_token:
core: no longer rely on webob exception inside get_or_404 function....
r1956 token = UserApiKeys.get_or_404(del_auth_token)
notifications: ported views to pyramid
r1920 token_data = token.get_api_data()
AuthTokenModel().delete(del_auth_token, c.user.user_id)
audit_logger.store_web(
'user.edit.token.delete', action_data={
'data': {'token': token_data, 'user': 'self'}},
user=self._rhodecode_user,)
Session().commit()
h.flash(_("Auth token successfully deleted"), category='success')
return HTTPFound(h.route_path('my_account_auth_tokens'))
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_emails', request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_emails(self):
_ = self.request.translate
c = self.load_default_context()
c.active = 'emails'
c.user_email_map = UserEmailMap.query()\
.filter(UserEmailMap.user == c.user).all()
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592
schema = user_schema.AddEmailSchema().bind(
username=c.user.username, user_emails=c.user.emails)
form = forms.RcForm(schema,
action=h.route_path('my_account_emails_add'),
buttons=(forms.buttons.save, forms.buttons.reset))
c.form = form
notifications: ported views to pyramid
r1920 return self._get_template_context(c)
@LoginRequired()
@NotAnonymous()
@CSRFRequired()
@view_config(
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 route_name='my_account_emails_add', request_method='POST',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
notifications: ported views to pyramid
r1920 def my_account_emails_add(self):
_ = self.request.translate
c = self.load_default_context()
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 c.active = 'emails'
notifications: ported views to pyramid
r1920
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 schema = user_schema.AddEmailSchema().bind(
username=c.user.username, user_emails=c.user.emails)
notifications: ported views to pyramid
r1920
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 form = forms.RcForm(
schema, action=h.route_path('my_account_emails_add'),
buttons=(forms.buttons.save, forms.buttons.reset))
controls = self.request.POST.items()
notifications: ported views to pyramid
r1920 try:
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 valid_data = form.validate(controls)
UserModel().add_extra_email(c.user.user_id, valid_data['email'])
notifications: ported views to pyramid
r1920 audit_logger.store_web(
'user.edit.email.add', action_data={
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 'data': {'email': valid_data['email'], 'user': 'self'}},
notifications: ported views to pyramid
r1920 user=self._rhodecode_user,)
Session().commit()
except formencode.Invalid as error:
h.flash(h.escape(error.error_dict['email']), category='error')
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 except forms.ValidationFailure as e:
c.user_email_map = UserEmailMap.query() \
.filter(UserEmailMap.user == c.user).all()
c.form = e
return self._get_template_context(c)
notifications: ported views to pyramid
r1920 except Exception:
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 log.exception("Exception adding email")
h.flash(_('Error occurred during adding email'),
notifications: ported views to pyramid
r1920 category='error')
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 else:
h.flash(_("Successfully added email"), category='success')
raise HTTPFound(self.request.route_path('my_account_emails'))
notifications: ported views to pyramid
r1920
@LoginRequired()
@NotAnonymous()
@CSRFRequired()
@view_config(
route_name='my_account_emails_delete', request_method='POST')
def my_account_emails_delete(self):
_ = self.request.translate
c = self.load_default_context()
del_email_id = self.request.POST.get('del_email_id')
if del_email_id:
core: no longer rely on webob exception inside get_or_404 function....
r1956 email = UserEmailMap.get_or_404(del_email_id).email
notifications: ported views to pyramid
r1920 UserModel().delete_extra_email(c.user.user_id, del_email_id)
audit_logger.store_web(
'user.edit.email.delete', action_data={
'data': {'email': email, 'user': 'self'}},
user=self._rhodecode_user,)
Session().commit()
h.flash(_("Email successfully deleted"),
category='success')
return HTTPFound(h.route_path('my_account_emails'))
@LoginRequired()
@NotAnonymous()
@CSRFRequired()
@view_config(
route_name='my_account_notifications_test_channelstream',
request_method='POST', renderer='json_ext')
def my_account_notifications_test_channelstream(self):
message = 'Test message sent via Channelstream by user: {}, on {}'.format(
self._rhodecode_user.username, datetime.datetime.now())
payload = {
# 'channel': 'broadcast',
'type': 'message',
'timestamp': datetime.datetime.utcnow(),
'user': 'system',
'pm_users': [self._rhodecode_user.username],
'message': {
'message': message,
'level': 'info',
'topic': '/notifications'
}
}
registry = self.request.registry
rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
channelstream_config = rhodecode_plugins.get('channelstream', {})
try:
channelstream_request(channelstream_config, [payload], '/message')
except ChannelstreamException as e:
log.exception('Failed to send channelstream data')
return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
return {"response": 'Channelstream data sent. '
'You should see a new live message now.'}
def _load_my_repos_data(self, watched=False):
if watched:
admin = False
follows_repos = Session().query(UserFollowing)\
.filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
.options(joinedload(UserFollowing.follows_repository))\
.all()
repo_list = [x.follows_repository for x in follows_repos]
else:
admin = True
repo_list = Repository.get_all_repos(
user_id=self._rhodecode_user.user_id)
repo_list = RepoList(repo_list, perm_set=[
'repository.read', 'repository.write', 'repository.admin'])
repos_data = RepoModel().get_repos_as_dict(
repo_list=repo_list, admin=admin)
# json used to render the grid
return json.dumps(repos_data)
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_repos', request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_repos(self):
c = self.load_default_context()
c.active = 'repos'
# json used to render the grid
c.data = self._load_my_repos_data()
return self._get_template_context(c)
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_watched', request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_watched(self):
c = self.load_default_context()
c.active = 'watched'
# json used to render the grid
c.data = self._load_my_repos_data(watched=True)
return self._get_template_context(c)
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_perms', request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_perms(self):
c = self.load_default_context()
c.active = 'perms'
c.perm_user = c.auth_user
return self._get_template_context(c)
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_notifications', request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_notifications(self):
c = self.load_default_context()
c.active = 'notifications'
return self._get_template_context(c)
@LoginRequired()
@NotAnonymous()
@CSRFRequired()
@view_config(
route_name='my_account_notifications_toggle_visibility',
request_method='POST', renderer='json_ext')
def my_notifications_toggle_visibility(self):
user = self._rhodecode_db_user
new_status = not user.user_data.get('notification_status', True)
user.update_userdata(notification_status=new_status)
Session().commit()
return user.user_data['notification_status']
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_edit',
request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_edit(self):
c = self.load_default_context()
c.active = 'profile_edit'
c.extern_type = c.user.extern_type
c.extern_name = c.user.extern_name
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 schema = user_schema.UserProfileSchema().bind(
username=c.user.username, user_emails=c.user.emails)
appstruct = {
'username': c.user.username,
'email': c.user.email,
'firstname': c.user.firstname,
'lastname': c.user.lastname,
}
c.form = forms.RcForm(
schema, appstruct=appstruct,
action=h.route_path('my_account_update'),
buttons=(forms.buttons.save, forms.buttons.reset))
notifications: ported views to pyramid
r1920
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 return self._get_template_context(c)
notifications: ported views to pyramid
r1920
@LoginRequired()
@NotAnonymous()
@CSRFRequired()
@view_config(
route_name='my_account_update',
request_method='POST',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_update(self):
_ = self.request.translate
c = self.load_default_context()
c.active = 'profile_edit'
c.perm_user = c.auth_user
c.extern_type = c.user.extern_type
c.extern_name = c.user.extern_name
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 schema = user_schema.UserProfileSchema().bind(
username=c.user.username, user_emails=c.user.emails)
form = forms.RcForm(
schema, buttons=(forms.buttons.save, forms.buttons.reset))
controls = self.request.POST.items()
notifications: ported views to pyramid
r1920 try:
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 valid_data = form.validate(controls)
notifications: ported views to pyramid
r1920 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
'new_password', 'password_confirmation']
if c.extern_type != "rhodecode":
# forbid updating username for external accounts
skip_attrs.append('username')
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 old_email = c.user.email
notifications: ported views to pyramid
r1920 UserModel().update_user(
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
**valid_data)
if old_email != valid_data['email']:
old = UserEmailMap.query() \
.filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
old.email = old_email
h.flash(_('Your account was updated successfully'), category='success')
notifications: ported views to pyramid
r1920 Session().commit()
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 except forms.ValidationFailure as e:
c.form = e
return self._get_template_context(c)
notifications: ported views to pyramid
r1920 except Exception:
log.exception("Exception updating user")
Bartłomiej Wołyńczyk
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
r2592 h.flash(_('Error occurred during update of user'),
category='error')
notifications: ported views to pyramid
r1920 raise HTTPFound(h.route_path('my_account_profile'))
def _get_pull_requests_list(self, statuses):
draw, start, limit = self._extract_chunk(self.request)
search_q, order_by, order_dir = self._extract_ordering(self.request)
_render = self.request.get_partial_renderer(
partial-renderer: use package resource format for templates....
r2313 'rhodecode:templates/data_table/_dt_elements.mako')
notifications: ported views to pyramid
r1920
pull_requests = PullRequestModel().get_im_participating_in(
user_id=self._rhodecode_user.user_id,
statuses=statuses,
offset=start, length=limit, order_by=order_by,
order_dir=order_dir)
pull_requests_total_count = PullRequestModel().count_im_participating_in(
user_id=self._rhodecode_user.user_id, statuses=statuses)
data = []
comments_model = CommentsModel()
for pr in pull_requests:
repo_id = pr.target_repo_id
comments = comments_model.get_all_comments(
repo_id, pull_request=pr)
owned = pr.user_id == self._rhodecode_user.user_id
data.append({
'target_repo': _render('pullrequest_target_repo',
pr.target_repo.repo_name),
'name': _render('pullrequest_name',
pr.pull_request_id, pr.target_repo.repo_name,
short=True),
'name_raw': pr.pull_request_id,
'status': _render('pullrequest_status',
pr.calculated_review_status()),
'title': _render(
'pullrequest_title', pr.title, pr.description),
'description': h.escape(pr.description),
'updated_on': _render('pullrequest_updated_on',
h.datetime_to_time(pr.updated_on)),
'updated_on_raw': h.datetime_to_time(pr.updated_on),
'created_on': _render('pullrequest_updated_on',
h.datetime_to_time(pr.created_on)),
'created_on_raw': h.datetime_to_time(pr.created_on),
'author': _render('pullrequest_author',
pr.author.full_contact, ),
'author_raw': pr.author.full_name,
'comments': _render('pullrequest_comments', len(comments)),
'comments_raw': len(comments),
'closed': pr.is_closed(),
'owned': owned
})
# json used to render the grid
data = ({
'draw': draw,
'data': data,
'recordsTotal': pull_requests_total_count,
'recordsFiltered': pull_requests_total_count,
})
return data
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_pullrequests',
request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_pullrequests(self):
c = self.load_default_context()
c.active = 'pullrequests'
req_get = self.request.GET
c.closed = str2bool(req_get.get('pr_show_closed'))
return self._get_template_context(c)
@LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_pullrequests_data',
request_method='GET', renderer='json_ext')
def my_account_pullrequests_data(self):
pylons: fixed code and test suite after removal of pylons.
r2358 self.load_default_context()
notifications: ported views to pyramid
r1920 req_get = self.request.GET
closed = str2bool(req_get.get('closed'))
statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
if closed:
statuses += [PullRequest.STATUS_CLOSED]
data = self._get_pull_requests_list(statuses=statuses)
return data
user-account: expose membership of user groups to users....
r2496 @LoginRequired()
@NotAnonymous()
@view_config(
route_name='my_account_user_group_membership',
request_method='GET',
renderer='rhodecode:templates/admin/my_account/my_account.mako')
def my_account_user_group_membership(self):
c = self.load_default_context()
c.active = 'user_group_membership'
groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
for group in self._rhodecode_db_user.group_member]
c.user_groups = json.dumps(groups)
return self._get_template_context(c)